feat(sessions): add a session manager to switch between sessions, tabs and panes and create new ones (#2721)
* write/read session metadata to disk for all sessions * switch session client side * fix tests * various adjustments * fix full screen focus bug in tiled panes * fix tests * fix permission sorting issue * cleanups * add session manager * fix tests * various cleanups * style(fmt): rustfmt * clear screen before switching sessions * I hate you clippy * truncate controls line to width * version session cache * attempt to fix plugin tests * style(fmt): rustfmt * another attempt to fix the tests in the ci
This commit is contained in:
parent
bf3c072d6d
commit
bc628abc12
54 changed files with 6397 additions and 370 deletions
15
Cargo.lock
generated
15
Cargo.lock
generated
|
|
@ -2868,6 +2868,17 @@ dependencies = [
|
||||||
"yaml-rust",
|
"yaml-rust",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "session-manager"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"ansi_term",
|
||||||
|
"chrono",
|
||||||
|
"fuzzy-matcher",
|
||||||
|
"unicode-width",
|
||||||
|
"zellij-tile",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha-1"
|
name = "sha-1"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
|
|
@ -3596,9 +3607,9 @@ checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-width"
|
name = "unicode-width"
|
||||||
version = "0.1.9"
|
version = "0.1.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
|
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unsafe-any-ors"
|
name = "unsafe-any-ors"
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ members = [
|
||||||
"default-plugins/strider",
|
"default-plugins/strider",
|
||||||
"default-plugins/tab-bar",
|
"default-plugins/tab-bar",
|
||||||
"default-plugins/fixture-plugin-for-tests",
|
"default-plugins/fixture-plugin-for-tests",
|
||||||
|
"default-plugins/session-manager",
|
||||||
"zellij-client",
|
"zellij-client",
|
||||||
"zellij-server",
|
"zellij-server",
|
||||||
"zellij-utils",
|
"zellij-utils",
|
||||||
|
|
|
||||||
2
default-plugins/session-manager/.cargo/config.toml
Normal file
2
default-plugins/session-manager/.cargo/config.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
[build]
|
||||||
|
target = "wasm32-wasi"
|
||||||
1
default-plugins/session-manager/.gitignore
vendored
Normal file
1
default-plugins/session-manager/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/target
|
||||||
2809
default-plugins/session-manager/Cargo.lock
generated
Normal file
2809
default-plugins/session-manager/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
12
default-plugins/session-manager/Cargo.toml
Normal file
12
default-plugins/session-manager/Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "session-manager"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Aram Drevekenin <aram@poor.dev>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
ansi_term = "0.12.1"
|
||||||
|
zellij-tile = { path = "../../zellij-tile" }
|
||||||
|
chrono = "0.4.0"
|
||||||
|
fuzzy-matcher = "0.3.7"
|
||||||
|
unicode-width = "0.1.10"
|
||||||
208
default-plugins/session-manager/src/main.rs
Normal file
208
default-plugins/session-manager/src/main.rs
Normal file
|
|
@ -0,0 +1,208 @@
|
||||||
|
mod session_list;
|
||||||
|
mod ui;
|
||||||
|
use zellij_tile::prelude::*;
|
||||||
|
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use ui::{
|
||||||
|
components::{render_controls_line, render_new_session_line, render_prompt, Colors},
|
||||||
|
SessionUiInfo,
|
||||||
|
};
|
||||||
|
|
||||||
|
use session_list::SessionList;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct State {
|
||||||
|
session_name: Option<String>,
|
||||||
|
sessions: SessionList,
|
||||||
|
selected_index: Option<usize>,
|
||||||
|
search_term: String,
|
||||||
|
new_session_name: Option<String>,
|
||||||
|
colors: Colors,
|
||||||
|
}
|
||||||
|
|
||||||
|
register_plugin!(State);
|
||||||
|
|
||||||
|
impl ZellijPlugin for State {
|
||||||
|
fn load(&mut self, _configuration: BTreeMap<String, String>) {
|
||||||
|
subscribe(&[
|
||||||
|
EventType::ModeUpdate,
|
||||||
|
EventType::SessionUpdate,
|
||||||
|
EventType::Key,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, event: Event) -> bool {
|
||||||
|
let mut should_render = false;
|
||||||
|
match event {
|
||||||
|
Event::ModeUpdate(mode_info) => {
|
||||||
|
self.colors = Colors::new(mode_info.style.colors);
|
||||||
|
should_render = true;
|
||||||
|
},
|
||||||
|
Event::Key(key) => {
|
||||||
|
should_render = self.handle_key(key);
|
||||||
|
},
|
||||||
|
Event::PermissionRequestResult(_result) => {
|
||||||
|
should_render = true;
|
||||||
|
},
|
||||||
|
Event::SessionUpdate(session_infos) => {
|
||||||
|
self.update_session_infos(session_infos);
|
||||||
|
should_render = true;
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
should_render
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&mut self, rows: usize, cols: usize) {
|
||||||
|
render_prompt(
|
||||||
|
self.new_session_name.is_some(),
|
||||||
|
&self.search_term,
|
||||||
|
self.colors,
|
||||||
|
);
|
||||||
|
let room_for_list = rows.saturating_sub(5); // search line and controls
|
||||||
|
self.sessions.update_rows(room_for_list);
|
||||||
|
let list = self
|
||||||
|
.sessions
|
||||||
|
.render(room_for_list, cols.saturating_sub(7), self.colors); // 7 for various ui
|
||||||
|
for line in list {
|
||||||
|
println!("{}", line.render());
|
||||||
|
}
|
||||||
|
render_new_session_line(
|
||||||
|
&self.new_session_name,
|
||||||
|
self.sessions.is_searching,
|
||||||
|
self.colors,
|
||||||
|
);
|
||||||
|
render_controls_line(self.sessions.is_searching, rows, cols, self.colors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
fn reset_selected_index(&mut self) {
|
||||||
|
self.selected_index = None;
|
||||||
|
}
|
||||||
|
fn handle_key(&mut self, key: Key) -> bool {
|
||||||
|
let mut should_render = false;
|
||||||
|
if let Key::Right = key {
|
||||||
|
if self.new_session_name.is_none() {
|
||||||
|
self.sessions.result_expand();
|
||||||
|
}
|
||||||
|
should_render = true;
|
||||||
|
} else if let Key::Left = key {
|
||||||
|
if self.new_session_name.is_none() {
|
||||||
|
self.sessions.result_shrink();
|
||||||
|
}
|
||||||
|
should_render = true;
|
||||||
|
} else if let Key::Down = key {
|
||||||
|
if self.new_session_name.is_none() {
|
||||||
|
self.sessions.move_selection_down();
|
||||||
|
}
|
||||||
|
should_render = true;
|
||||||
|
} else if let Key::Up = key {
|
||||||
|
if self.new_session_name.is_none() {
|
||||||
|
self.sessions.move_selection_up();
|
||||||
|
}
|
||||||
|
should_render = true;
|
||||||
|
} else if let Key::Char(character) = key {
|
||||||
|
if character == '\n' {
|
||||||
|
self.handle_selection();
|
||||||
|
} else if let Some(new_session_name) = self.new_session_name.as_mut() {
|
||||||
|
new_session_name.push(character);
|
||||||
|
} else {
|
||||||
|
self.search_term.push(character);
|
||||||
|
self.sessions
|
||||||
|
.update_search_term(&self.search_term, &self.colors);
|
||||||
|
}
|
||||||
|
should_render = true;
|
||||||
|
} else if let Key::Backspace = key {
|
||||||
|
if let Some(new_session_name) = self.new_session_name.as_mut() {
|
||||||
|
if new_session_name.is_empty() {
|
||||||
|
self.new_session_name = None;
|
||||||
|
} else {
|
||||||
|
new_session_name.pop();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.search_term.pop();
|
||||||
|
self.sessions
|
||||||
|
.update_search_term(&self.search_term, &self.colors);
|
||||||
|
}
|
||||||
|
should_render = true;
|
||||||
|
} else if let Key::Ctrl('w') = key {
|
||||||
|
if self.sessions.is_searching {
|
||||||
|
// no-op
|
||||||
|
} else if self.new_session_name.is_some() {
|
||||||
|
self.new_session_name = None;
|
||||||
|
} else {
|
||||||
|
self.new_session_name = Some(String::new());
|
||||||
|
}
|
||||||
|
should_render = true;
|
||||||
|
} else if let Key::Ctrl('c') = key {
|
||||||
|
if let Some(new_session_name) = self.new_session_name.as_mut() {
|
||||||
|
if new_session_name.is_empty() {
|
||||||
|
self.new_session_name = None;
|
||||||
|
} else {
|
||||||
|
new_session_name.clear()
|
||||||
|
}
|
||||||
|
} else if !self.search_term.is_empty() {
|
||||||
|
self.search_term.clear();
|
||||||
|
self.sessions
|
||||||
|
.update_search_term(&self.search_term, &self.colors);
|
||||||
|
self.reset_selected_index();
|
||||||
|
} else {
|
||||||
|
self.reset_selected_index();
|
||||||
|
hide_self();
|
||||||
|
}
|
||||||
|
should_render = true;
|
||||||
|
} else if let Key::Esc = key {
|
||||||
|
hide_self();
|
||||||
|
}
|
||||||
|
should_render
|
||||||
|
}
|
||||||
|
fn handle_selection(&mut self) {
|
||||||
|
if let Some(new_session_name) = &self.new_session_name {
|
||||||
|
if new_session_name.is_empty() {
|
||||||
|
switch_session(None);
|
||||||
|
} else if self.session_name.as_ref() == Some(new_session_name) {
|
||||||
|
// noop - we're already here!
|
||||||
|
self.new_session_name = None;
|
||||||
|
} else {
|
||||||
|
switch_session(Some(new_session_name));
|
||||||
|
}
|
||||||
|
} else if let Some(selected_session_name) = self.sessions.get_selected_session_name() {
|
||||||
|
let selected_tab = self.sessions.get_selected_tab_position();
|
||||||
|
let selected_pane = self.sessions.get_selected_pane_id();
|
||||||
|
let is_current_session = self.sessions.selected_is_current_session();
|
||||||
|
if is_current_session {
|
||||||
|
if let Some((pane_id, is_plugin)) = selected_pane {
|
||||||
|
if is_plugin {
|
||||||
|
focus_plugin_pane(pane_id, true);
|
||||||
|
} else {
|
||||||
|
focus_terminal_pane(pane_id, true);
|
||||||
|
}
|
||||||
|
} else if let Some(tab_position) = selected_tab {
|
||||||
|
go_to_tab(tab_position as u32);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch_session_with_focus(&selected_session_name, selected_tab, selected_pane);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hide_self();
|
||||||
|
}
|
||||||
|
fn update_session_infos(&mut self, session_infos: Vec<SessionInfo>) {
|
||||||
|
let session_infos: Vec<SessionUiInfo> = session_infos
|
||||||
|
.iter()
|
||||||
|
.map(|s| SessionUiInfo::from_session_info(s))
|
||||||
|
.collect();
|
||||||
|
let current_session_name = session_infos.iter().find_map(|s| {
|
||||||
|
if s.is_current_session {
|
||||||
|
Some(s.name.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if let Some(current_session_name) = current_session_name {
|
||||||
|
self.session_name = Some(current_session_name);
|
||||||
|
}
|
||||||
|
self.sessions.set_sessions(session_infos);
|
||||||
|
}
|
||||||
|
}
|
||||||
381
default-plugins/session-manager/src/session_list.rs
Normal file
381
default-plugins/session-manager/src/session_list.rs
Normal file
|
|
@ -0,0 +1,381 @@
|
||||||
|
use fuzzy_matcher::skim::SkimMatcherV2;
|
||||||
|
use fuzzy_matcher::FuzzyMatcher;
|
||||||
|
|
||||||
|
use crate::ui::{
|
||||||
|
components::{Colors, LineToRender, ListItem},
|
||||||
|
SessionUiInfo,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct SessionList {
|
||||||
|
pub session_ui_infos: Vec<SessionUiInfo>,
|
||||||
|
pub selected_index: SelectedIndex,
|
||||||
|
pub selected_search_index: Option<usize>,
|
||||||
|
pub search_results: Vec<SearchResult>,
|
||||||
|
pub is_searching: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SessionList {
|
||||||
|
pub fn set_sessions(&mut self, mut session_ui_infos: Vec<SessionUiInfo>) {
|
||||||
|
session_ui_infos.sort_unstable_by(|a, b| {
|
||||||
|
if a.is_current_session {
|
||||||
|
std::cmp::Ordering::Less
|
||||||
|
} else if b.is_current_session {
|
||||||
|
std::cmp::Ordering::Greater
|
||||||
|
} else {
|
||||||
|
a.name.cmp(&b.name)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
self.session_ui_infos = session_ui_infos;
|
||||||
|
}
|
||||||
|
pub fn update_search_term(&mut self, search_term: &str, colors: &Colors) {
|
||||||
|
let mut flattened_assets = self.flatten_assets(colors);
|
||||||
|
let mut matches = vec![];
|
||||||
|
let matcher = SkimMatcherV2::default().use_cache(true);
|
||||||
|
for (list_item, session_name, tab_position, pane_id, is_current_session) in
|
||||||
|
flattened_assets.drain(..)
|
||||||
|
{
|
||||||
|
if let Some((score, indices)) = matcher.fuzzy_indices(&list_item.name, &search_term) {
|
||||||
|
matches.push(SearchResult::new(
|
||||||
|
score,
|
||||||
|
indices,
|
||||||
|
list_item,
|
||||||
|
session_name,
|
||||||
|
tab_position,
|
||||||
|
pane_id,
|
||||||
|
is_current_session,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
matches.sort_by(|a, b| b.score.cmp(&a.score));
|
||||||
|
self.search_results = matches;
|
||||||
|
self.is_searching = !search_term.is_empty();
|
||||||
|
self.selected_search_index = Some(0);
|
||||||
|
}
|
||||||
|
fn flatten_assets(
|
||||||
|
&self,
|
||||||
|
colors: &Colors,
|
||||||
|
) -> Vec<(ListItem, String, Option<usize>, Option<(u32, bool)>, bool)> {
|
||||||
|
// list_item, session_name, tab_position, (pane_id, is_plugin), is_current_session
|
||||||
|
let mut list_items = vec![];
|
||||||
|
for session in &self.session_ui_infos {
|
||||||
|
let session_name = session.name.clone();
|
||||||
|
let is_current_session = session.is_current_session;
|
||||||
|
list_items.push((
|
||||||
|
ListItem::from_session_info(session, *colors),
|
||||||
|
session_name.clone(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
is_current_session,
|
||||||
|
));
|
||||||
|
for tab in &session.tabs {
|
||||||
|
let tab_position = tab.position;
|
||||||
|
list_items.push((
|
||||||
|
ListItem::from_tab_info(session, tab, *colors),
|
||||||
|
session_name.clone(),
|
||||||
|
Some(tab_position),
|
||||||
|
None,
|
||||||
|
is_current_session,
|
||||||
|
));
|
||||||
|
for pane in &tab.panes {
|
||||||
|
let pane_id = (pane.pane_id, pane.is_plugin);
|
||||||
|
list_items.push((
|
||||||
|
ListItem::from_pane_info(session, tab, pane, *colors),
|
||||||
|
session_name.clone(),
|
||||||
|
Some(tab_position),
|
||||||
|
Some(pane_id),
|
||||||
|
is_current_session,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list_items
|
||||||
|
}
|
||||||
|
pub fn get_selected_session_name(&self) -> Option<String> {
|
||||||
|
if self.is_searching {
|
||||||
|
self.selected_search_index
|
||||||
|
.and_then(|i| self.search_results.get(i))
|
||||||
|
.map(|s| s.session_name.clone())
|
||||||
|
} else {
|
||||||
|
self.selected_index
|
||||||
|
.0
|
||||||
|
.and_then(|i| self.session_ui_infos.get(i))
|
||||||
|
.map(|s_i| s_i.name.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn selected_is_current_session(&self) -> bool {
|
||||||
|
if self.is_searching {
|
||||||
|
self.selected_search_index
|
||||||
|
.and_then(|i| self.search_results.get(i))
|
||||||
|
.map(|s| s.is_current_session)
|
||||||
|
.unwrap_or(false)
|
||||||
|
} else {
|
||||||
|
self.selected_index
|
||||||
|
.0
|
||||||
|
.and_then(|i| self.session_ui_infos.get(i))
|
||||||
|
.map(|s_i| s_i.is_current_session)
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn get_selected_tab_position(&self) -> Option<usize> {
|
||||||
|
if self.is_searching {
|
||||||
|
self.selected_search_index
|
||||||
|
.and_then(|i| self.search_results.get(i))
|
||||||
|
.and_then(|s| s.tab_position)
|
||||||
|
} else {
|
||||||
|
self.selected_index
|
||||||
|
.0
|
||||||
|
.and_then(|i| self.session_ui_infos.get(i))
|
||||||
|
.and_then(|s_i| {
|
||||||
|
self.selected_index
|
||||||
|
.1
|
||||||
|
.and_then(|i| s_i.tabs.get(i))
|
||||||
|
.map(|t| t.position)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn get_selected_pane_id(&self) -> Option<(u32, bool)> {
|
||||||
|
// (pane_id, is_plugin)
|
||||||
|
if self.is_searching {
|
||||||
|
self.selected_search_index
|
||||||
|
.and_then(|i| self.search_results.get(i))
|
||||||
|
.and_then(|s| s.pane_id)
|
||||||
|
} else {
|
||||||
|
self.selected_index
|
||||||
|
.0
|
||||||
|
.and_then(|i| self.session_ui_infos.get(i))
|
||||||
|
.and_then(|s_i| {
|
||||||
|
self.selected_index
|
||||||
|
.1
|
||||||
|
.and_then(|i| s_i.tabs.get(i))
|
||||||
|
.and_then(|t| {
|
||||||
|
self.selected_index
|
||||||
|
.2
|
||||||
|
.and_then(|i| t.panes.get(i))
|
||||||
|
.map(|p| (p.pane_id, p.is_plugin))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn move_selection_down(&mut self) {
|
||||||
|
if self.is_searching {
|
||||||
|
match self.selected_search_index.as_mut() {
|
||||||
|
Some(search_index) => {
|
||||||
|
*search_index = search_index.saturating_add(1);
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
if !self.search_results.is_empty() {
|
||||||
|
self.selected_search_index = Some(0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match self.selected_index {
|
||||||
|
SelectedIndex(None, None, None) => {
|
||||||
|
if !self.session_ui_infos.is_empty() {
|
||||||
|
self.selected_index.0 = Some(0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
SelectedIndex(Some(selected_session), None, None) => {
|
||||||
|
if self.session_ui_infos.len() > selected_session + 1 {
|
||||||
|
self.selected_index.0 = Some(selected_session + 1);
|
||||||
|
} else {
|
||||||
|
self.selected_index.0 = None;
|
||||||
|
self.selected_index.1 = None;
|
||||||
|
self.selected_index.2 = None;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
SelectedIndex(Some(selected_session), Some(selected_tab), None) => {
|
||||||
|
if self
|
||||||
|
.get_session(selected_session)
|
||||||
|
.map(|s| s.tabs.len() > selected_tab + 1)
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
self.selected_index.1 = Some(selected_tab + 1);
|
||||||
|
} else {
|
||||||
|
self.selected_index.1 = Some(0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
SelectedIndex(Some(selected_session), Some(selected_tab), Some(selected_pane)) => {
|
||||||
|
if self
|
||||||
|
.get_session(selected_session)
|
||||||
|
.and_then(|s| s.tabs.get(selected_tab))
|
||||||
|
.map(|t| t.panes.len() > selected_pane + 1)
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
self.selected_index.2 = Some(selected_pane + 1);
|
||||||
|
} else {
|
||||||
|
self.selected_index.2 = Some(0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn move_selection_up(&mut self) {
|
||||||
|
if self.is_searching {
|
||||||
|
match self.selected_search_index.as_mut() {
|
||||||
|
Some(search_index) => {
|
||||||
|
*search_index = search_index.saturating_sub(1);
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
if !self.search_results.is_empty() {
|
||||||
|
self.selected_search_index = Some(0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match self.selected_index {
|
||||||
|
SelectedIndex(None, None, None) => {
|
||||||
|
if !self.session_ui_infos.is_empty() {
|
||||||
|
self.selected_index.0 = Some(self.session_ui_infos.len().saturating_sub(1))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
SelectedIndex(Some(selected_session), None, None) => {
|
||||||
|
if selected_session > 0 {
|
||||||
|
self.selected_index.0 = Some(selected_session - 1);
|
||||||
|
} else {
|
||||||
|
self.selected_index.0 = None;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
SelectedIndex(Some(selected_session), Some(selected_tab), None) => {
|
||||||
|
if selected_tab > 0 {
|
||||||
|
self.selected_index.1 = Some(selected_tab - 1);
|
||||||
|
} else {
|
||||||
|
let tab_count = self
|
||||||
|
.get_session(selected_session)
|
||||||
|
.map(|s| s.tabs.len())
|
||||||
|
.unwrap_or(0);
|
||||||
|
self.selected_index.1 = Some(tab_count.saturating_sub(1))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
SelectedIndex(Some(selected_session), Some(selected_tab), Some(selected_pane)) => {
|
||||||
|
if selected_pane > 0 {
|
||||||
|
self.selected_index.2 = Some(selected_pane - 1);
|
||||||
|
} else {
|
||||||
|
let pane_count = self
|
||||||
|
.get_session(selected_session)
|
||||||
|
.and_then(|s| s.tabs.get(selected_tab))
|
||||||
|
.map(|t| t.panes.len())
|
||||||
|
.unwrap_or(0);
|
||||||
|
self.selected_index.2 = Some(pane_count.saturating_sub(1))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn get_session(&self, index: usize) -> Option<&SessionUiInfo> {
|
||||||
|
self.session_ui_infos.get(index)
|
||||||
|
}
|
||||||
|
pub fn result_expand(&mut self) {
|
||||||
|
// we can't move this to SelectedIndex because the borrow checker is mean
|
||||||
|
match self.selected_index {
|
||||||
|
SelectedIndex(Some(selected_session), None, None) => {
|
||||||
|
let selected_session_has_tabs = self
|
||||||
|
.get_session(selected_session)
|
||||||
|
.map(|s| !s.tabs.is_empty())
|
||||||
|
.unwrap_or(false);
|
||||||
|
if selected_session_has_tabs {
|
||||||
|
self.selected_index.1 = Some(0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
SelectedIndex(Some(selected_session), Some(selected_tab), None) => {
|
||||||
|
let selected_tab_has_panes = self
|
||||||
|
.get_session(selected_session)
|
||||||
|
.and_then(|s| s.tabs.get(selected_tab))
|
||||||
|
.map(|t| !t.panes.is_empty())
|
||||||
|
.unwrap_or(false);
|
||||||
|
if selected_tab_has_panes {
|
||||||
|
self.selected_index.2 = Some(0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn result_shrink(&mut self) {
|
||||||
|
self.selected_index.result_shrink();
|
||||||
|
}
|
||||||
|
pub fn update_rows(&mut self, rows: usize) {
|
||||||
|
if let Some(search_result_rows_until_selected) = self.selected_search_index.map(|i| {
|
||||||
|
self.search_results
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.take(i + 1)
|
||||||
|
.fold(0, |acc, s| acc + s.1.lines_to_render())
|
||||||
|
}) {
|
||||||
|
if search_result_rows_until_selected > rows
|
||||||
|
|| self.selected_search_index >= Some(self.search_results.len())
|
||||||
|
{
|
||||||
|
self.selected_search_index = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct SelectedIndex(pub Option<usize>, pub Option<usize>, pub Option<usize>);
|
||||||
|
|
||||||
|
impl SelectedIndex {
|
||||||
|
pub fn tabs_are_visible(&self) -> bool {
|
||||||
|
self.1.is_some()
|
||||||
|
}
|
||||||
|
pub fn panes_are_visible(&self) -> bool {
|
||||||
|
self.2.is_some()
|
||||||
|
}
|
||||||
|
pub fn selected_tab_index(&self) -> Option<usize> {
|
||||||
|
self.1
|
||||||
|
}
|
||||||
|
pub fn session_index_is_selected(&self, index: usize) -> bool {
|
||||||
|
self.0 == Some(index)
|
||||||
|
}
|
||||||
|
pub fn result_shrink(&mut self) {
|
||||||
|
match self {
|
||||||
|
SelectedIndex(Some(_selected_session), None, None) => self.0 = None,
|
||||||
|
SelectedIndex(Some(_selected_session), Some(_selected_tab), None) => self.1 = None,
|
||||||
|
SelectedIndex(Some(_selected_session), Some(_selected_tab), Some(_selected_pane)) => {
|
||||||
|
self.2 = None
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SearchResult {
|
||||||
|
score: i64,
|
||||||
|
indices: Vec<usize>,
|
||||||
|
list_item: ListItem,
|
||||||
|
session_name: String,
|
||||||
|
tab_position: Option<usize>,
|
||||||
|
pane_id: Option<(u32, bool)>,
|
||||||
|
is_current_session: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SearchResult {
|
||||||
|
pub fn new(
|
||||||
|
score: i64,
|
||||||
|
indices: Vec<usize>,
|
||||||
|
list_item: ListItem,
|
||||||
|
session_name: String,
|
||||||
|
tab_position: Option<usize>,
|
||||||
|
pane_id: Option<(u32, bool)>,
|
||||||
|
is_current_session: bool,
|
||||||
|
) -> Self {
|
||||||
|
SearchResult {
|
||||||
|
score,
|
||||||
|
indices,
|
||||||
|
list_item,
|
||||||
|
session_name,
|
||||||
|
tab_position,
|
||||||
|
pane_id,
|
||||||
|
is_current_session,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn lines_to_render(&self) -> usize {
|
||||||
|
self.list_item.line_count()
|
||||||
|
}
|
||||||
|
pub fn render(&self, max_width: usize) -> Vec<LineToRender> {
|
||||||
|
self.list_item.render(Some(self.indices.clone()), max_width)
|
||||||
|
}
|
||||||
|
}
|
||||||
577
default-plugins/session-manager/src/ui/components.rs
Normal file
577
default-plugins/session-manager/src/ui/components.rs
Normal file
|
|
@ -0,0 +1,577 @@
|
||||||
|
use unicode_width::UnicodeWidthChar;
|
||||||
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
use zellij_tile::prelude::*;
|
||||||
|
|
||||||
|
use crate::ui::{PaneUiInfo, SessionUiInfo, TabUiInfo};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ListItem {
|
||||||
|
pub name: String,
|
||||||
|
pub session_name: Option<Vec<UiSpan>>,
|
||||||
|
pub tab_name: Option<Vec<UiSpan>>,
|
||||||
|
pub pane_name: Option<Vec<UiSpan>>,
|
||||||
|
colors: Colors,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ListItem {
|
||||||
|
pub fn from_session_info(session_ui_info: &SessionUiInfo, colors: Colors) -> Self {
|
||||||
|
let session_ui_line = build_session_ui_line(session_ui_info, colors);
|
||||||
|
ListItem {
|
||||||
|
name: session_ui_info.name.clone(),
|
||||||
|
session_name: Some(session_ui_line),
|
||||||
|
tab_name: None,
|
||||||
|
pane_name: None,
|
||||||
|
colors,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn from_tab_info(
|
||||||
|
session_ui_info: &SessionUiInfo,
|
||||||
|
tab_ui_info: &TabUiInfo,
|
||||||
|
colors: Colors,
|
||||||
|
) -> Self {
|
||||||
|
let session_ui_line = build_session_ui_line(session_ui_info, colors);
|
||||||
|
let tab_ui_line = build_tab_ui_line(tab_ui_info, colors);
|
||||||
|
ListItem {
|
||||||
|
name: tab_ui_info.name.clone(),
|
||||||
|
session_name: Some(session_ui_line),
|
||||||
|
tab_name: Some(tab_ui_line),
|
||||||
|
pane_name: None,
|
||||||
|
colors,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn from_pane_info(
|
||||||
|
session_ui_info: &SessionUiInfo,
|
||||||
|
tab_ui_info: &TabUiInfo,
|
||||||
|
pane_ui_info: &PaneUiInfo,
|
||||||
|
colors: Colors,
|
||||||
|
) -> Self {
|
||||||
|
let session_ui_line = build_session_ui_line(session_ui_info, colors);
|
||||||
|
let tab_ui_line = build_tab_ui_line(tab_ui_info, colors);
|
||||||
|
let pane_ui_line = build_pane_ui_line(pane_ui_info, colors);
|
||||||
|
ListItem {
|
||||||
|
name: pane_ui_info.name.clone(),
|
||||||
|
session_name: Some(session_ui_line),
|
||||||
|
tab_name: Some(tab_ui_line),
|
||||||
|
pane_name: Some(pane_ui_line),
|
||||||
|
colors,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn line_count(&self) -> usize {
|
||||||
|
let mut line_count = 0;
|
||||||
|
if self.session_name.is_some() {
|
||||||
|
line_count += 1
|
||||||
|
};
|
||||||
|
if self.tab_name.is_some() {
|
||||||
|
line_count += 1
|
||||||
|
};
|
||||||
|
if self.pane_name.is_some() {
|
||||||
|
line_count += 1
|
||||||
|
};
|
||||||
|
line_count
|
||||||
|
}
|
||||||
|
pub fn render(&self, indices: Option<Vec<usize>>, max_cols: usize) -> Vec<LineToRender> {
|
||||||
|
let mut lines_to_render = vec![];
|
||||||
|
if let Some(session_name) = &self.session_name {
|
||||||
|
let indices = if self.tab_name.is_none() && self.pane_name.is_none() {
|
||||||
|
indices.clone()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let mut line_to_render = LineToRender::new(self.colors);
|
||||||
|
let mut remaining_cols = max_cols;
|
||||||
|
for span in session_name {
|
||||||
|
span.render(
|
||||||
|
indices
|
||||||
|
.clone()
|
||||||
|
.map(|i| (SpanStyle::ForegroundBold(self.colors.palette.magenta), i)),
|
||||||
|
&mut line_to_render,
|
||||||
|
&mut remaining_cols,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
lines_to_render.push(line_to_render);
|
||||||
|
}
|
||||||
|
if let Some(tab_name) = &self.tab_name {
|
||||||
|
let indices = if self.pane_name.is_none() {
|
||||||
|
indices.clone()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let mut line_to_render = LineToRender::new(self.colors);
|
||||||
|
let mut remaining_cols = max_cols;
|
||||||
|
for span in tab_name {
|
||||||
|
span.render(
|
||||||
|
indices
|
||||||
|
.clone()
|
||||||
|
.map(|i| (SpanStyle::ForegroundBold(self.colors.palette.magenta), i)),
|
||||||
|
&mut line_to_render,
|
||||||
|
&mut remaining_cols,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
lines_to_render.push(line_to_render);
|
||||||
|
}
|
||||||
|
if let Some(pane_name) = &self.pane_name {
|
||||||
|
let mut line_to_render = LineToRender::new(self.colors);
|
||||||
|
let mut remaining_cols = max_cols;
|
||||||
|
for span in pane_name {
|
||||||
|
span.render(
|
||||||
|
indices
|
||||||
|
.clone()
|
||||||
|
.map(|i| (SpanStyle::ForegroundBold(self.colors.palette.magenta), i)),
|
||||||
|
&mut line_to_render,
|
||||||
|
&mut remaining_cols,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
lines_to_render.push(line_to_render);
|
||||||
|
}
|
||||||
|
lines_to_render
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum UiSpan {
|
||||||
|
UiSpanTelescope(UiSpanTelescope),
|
||||||
|
TruncatableUiSpan(TruncatableUiSpan),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiSpan {
|
||||||
|
pub fn render(
|
||||||
|
&self,
|
||||||
|
indices: Option<(SpanStyle, Vec<usize>)>,
|
||||||
|
line_to_render: &mut LineToRender,
|
||||||
|
remaining_cols: &mut usize,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
UiSpan::UiSpanTelescope(ui_span_telescope) => {
|
||||||
|
ui_span_telescope.render(line_to_render, remaining_cols)
|
||||||
|
},
|
||||||
|
UiSpan::TruncatableUiSpan(truncatable_ui_span) => {
|
||||||
|
truncatable_ui_span.render(indices, line_to_render, remaining_cols)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)] // in the future this will be moved to be its own component
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum SpanStyle {
|
||||||
|
None,
|
||||||
|
Bold,
|
||||||
|
Foreground(PaletteColor),
|
||||||
|
ForegroundBold(PaletteColor),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpanStyle {
|
||||||
|
pub fn style_string(&self, to_style: &str) -> String {
|
||||||
|
match self {
|
||||||
|
SpanStyle::None => to_style.to_owned(),
|
||||||
|
SpanStyle::Bold => format!("\u{1b}[1m{}\u{1b}[22m", to_style),
|
||||||
|
SpanStyle::Foreground(color) => match color {
|
||||||
|
PaletteColor::EightBit(byte) => {
|
||||||
|
format!("\u{1b}[38;5;{byte}m{}\u{1b}[39m", to_style)
|
||||||
|
},
|
||||||
|
PaletteColor::Rgb((r, g, b)) => {
|
||||||
|
format!("\u{1b}[38;2;{};{};{}m{}\u{1b}[39m", r, g, b, to_style)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SpanStyle::ForegroundBold(color) => match color {
|
||||||
|
PaletteColor::EightBit(byte) => {
|
||||||
|
format!("\u{1b}[38;5;{byte};1m{}\u{1b}[39;22m", to_style)
|
||||||
|
},
|
||||||
|
PaletteColor::Rgb((r, g, b)) => {
|
||||||
|
format!("\u{1b}[38;2;{};{};{};1m{}\u{1b}[39;22m", r, g, b, to_style)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SpanStyle {
|
||||||
|
fn default() -> Self {
|
||||||
|
SpanStyle::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct TruncatableUiSpan {
|
||||||
|
text: String,
|
||||||
|
style: SpanStyle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TruncatableUiSpan {
|
||||||
|
pub fn new(text: String, style: SpanStyle) -> Self {
|
||||||
|
TruncatableUiSpan { text, style }
|
||||||
|
}
|
||||||
|
pub fn render(
|
||||||
|
&self,
|
||||||
|
indices: Option<(SpanStyle, Vec<usize>)>,
|
||||||
|
line_to_render: &mut LineToRender,
|
||||||
|
remaining_cols: &mut usize,
|
||||||
|
) {
|
||||||
|
let mut rendered = String::new();
|
||||||
|
let truncated = if *remaining_cols >= self.text.width() {
|
||||||
|
self.text.clone()
|
||||||
|
} else {
|
||||||
|
let mut truncated = String::new();
|
||||||
|
for character in self.text.chars() {
|
||||||
|
if truncated.width() + character.width().unwrap_or(0) <= *remaining_cols {
|
||||||
|
truncated.push(character);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
truncated
|
||||||
|
};
|
||||||
|
match indices {
|
||||||
|
Some((index_style, indices)) => {
|
||||||
|
for (i, character) in truncated.chars().enumerate() {
|
||||||
|
// TODO: optimize this by splitting the string up by its indices and only pushing those
|
||||||
|
// chu8nks
|
||||||
|
if indices.contains(&i) {
|
||||||
|
rendered.push_str(&index_style.style_string(&character.to_string()));
|
||||||
|
} else {
|
||||||
|
rendered.push_str(&self.style.style_string(&character.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
rendered.push_str(&self.style.style_string(&truncated));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
*remaining_cols = remaining_cols.saturating_sub(truncated.width());
|
||||||
|
line_to_render.append(&rendered);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct UiSpanTelescope(Vec<StringAndLength>);
|
||||||
|
|
||||||
|
impl UiSpanTelescope {
|
||||||
|
pub fn new(string_and_lengths: Vec<StringAndLength>) -> Self {
|
||||||
|
UiSpanTelescope(string_and_lengths)
|
||||||
|
}
|
||||||
|
pub fn render(&self, line_to_render: &mut LineToRender, remaining_cols: &mut usize) {
|
||||||
|
for string_and_length in &self.0 {
|
||||||
|
if string_and_length.length < *remaining_cols {
|
||||||
|
line_to_render.append(&string_and_length.string);
|
||||||
|
*remaining_cols -= string_and_length.length;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
pub struct StringAndLength {
|
||||||
|
pub string: String,
|
||||||
|
pub length: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StringAndLength {
|
||||||
|
pub fn new(string: String, length: usize) -> Self {
|
||||||
|
StringAndLength { string, length }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct LineToRender {
|
||||||
|
line: String,
|
||||||
|
is_selected: bool,
|
||||||
|
truncated_result_count: usize,
|
||||||
|
colors: Colors,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LineToRender {
|
||||||
|
pub fn new(colors: Colors) -> Self {
|
||||||
|
LineToRender {
|
||||||
|
line: String::default(),
|
||||||
|
is_selected: false,
|
||||||
|
truncated_result_count: 0,
|
||||||
|
colors,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn append(&mut self, to_append: &str) {
|
||||||
|
self.line.push_str(to_append)
|
||||||
|
}
|
||||||
|
pub fn make_selected(&mut self) {
|
||||||
|
self.is_selected = true;
|
||||||
|
match self.colors.palette.gray {
|
||||||
|
PaletteColor::EightBit(byte) => {
|
||||||
|
self.line = format!(
|
||||||
|
"\u{1b}[48;5;{byte}m\u{1b}[K\r\u{1b}[48;5;{byte}m{}",
|
||||||
|
self.line
|
||||||
|
);
|
||||||
|
},
|
||||||
|
PaletteColor::Rgb((r, g, b)) => {
|
||||||
|
self.line = format!(
|
||||||
|
"\u{1b}[48;2;{};{};{}m\u{1b}[K\r\u{1b}[48;5;{};{};{}m{}",
|
||||||
|
r, g, b, r, g, b, self.line
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn render(&self) -> String {
|
||||||
|
let mut line = self.line.clone();
|
||||||
|
|
||||||
|
let more = if self.truncated_result_count > 0 {
|
||||||
|
self.colors
|
||||||
|
.red(&format!(" [+{}]", self.truncated_result_count))
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
line.push_str(&more);
|
||||||
|
if self.is_selected {
|
||||||
|
self.line.clone()
|
||||||
|
} else {
|
||||||
|
format!("\u{1b}[49m{}", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn add_truncated_results(&mut self, result_count: usize) {
|
||||||
|
self.truncated_result_count += result_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_session_ui_line(session_ui_info: &SessionUiInfo, colors: Colors) -> Vec<UiSpan> {
|
||||||
|
let mut ui_spans = vec![];
|
||||||
|
let tab_count_text = session_ui_info.tabs.len();
|
||||||
|
let total_pane_count_text = session_ui_info
|
||||||
|
.tabs
|
||||||
|
.iter()
|
||||||
|
.fold(0, |acc, tab| acc + tab.panes.len());
|
||||||
|
let tab_count = format!("{}", tab_count_text);
|
||||||
|
let tab_count_styled = colors.cyan(&tab_count);
|
||||||
|
let total_pane_count = format!("{}", total_pane_count_text);
|
||||||
|
let total_pane_count_styled = colors.green(&total_pane_count);
|
||||||
|
let session_name = &session_ui_info.name;
|
||||||
|
let connected_users = format!("{}", session_ui_info.connected_users);
|
||||||
|
let connected_users_styled = colors.orange(&connected_users);
|
||||||
|
let session_bullet_span =
|
||||||
|
UiSpan::UiSpanTelescope(UiSpanTelescope::new(vec![StringAndLength::new(
|
||||||
|
format!(" > "),
|
||||||
|
3,
|
||||||
|
)]));
|
||||||
|
let session_name_span = UiSpan::TruncatableUiSpan(TruncatableUiSpan::new(
|
||||||
|
session_name.clone(),
|
||||||
|
SpanStyle::ForegroundBold(colors.palette.orange),
|
||||||
|
));
|
||||||
|
let tab_and_pane_count = UiSpan::UiSpanTelescope(UiSpanTelescope::new(vec![
|
||||||
|
StringAndLength::new(
|
||||||
|
format!(" ({tab_count_styled} tabs, {total_pane_count_styled} panes)"),
|
||||||
|
2 + tab_count.width() + 7 + total_pane_count.width() + 7,
|
||||||
|
),
|
||||||
|
StringAndLength::new(
|
||||||
|
format!(" ({tab_count_styled}, {total_pane_count_styled})"),
|
||||||
|
2 + tab_count.width() + 2 + total_pane_count.width() + 3,
|
||||||
|
),
|
||||||
|
]));
|
||||||
|
let connected_users_count = UiSpan::UiSpanTelescope(UiSpanTelescope::new(vec![
|
||||||
|
StringAndLength::new(
|
||||||
|
format!(" [{connected_users_styled} connected users]"),
|
||||||
|
2 + connected_users.width() + 17,
|
||||||
|
),
|
||||||
|
StringAndLength::new(
|
||||||
|
format!(" [{connected_users_styled}]"),
|
||||||
|
2 + connected_users.width() + 1,
|
||||||
|
),
|
||||||
|
]));
|
||||||
|
ui_spans.push(session_bullet_span);
|
||||||
|
ui_spans.push(session_name_span);
|
||||||
|
ui_spans.push(tab_and_pane_count);
|
||||||
|
ui_spans.push(connected_users_count);
|
||||||
|
if session_ui_info.is_current_session {
|
||||||
|
let current_session_indication = UiSpan::UiSpanTelescope(UiSpanTelescope::new(vec![
|
||||||
|
StringAndLength::new(colors.orange(&format!(" <CURRENT SESSION>")), 18),
|
||||||
|
StringAndLength::new(colors.orange(&format!(" <CURRENT>")), 10),
|
||||||
|
StringAndLength::new(colors.orange(&format!(" <C>")), 4),
|
||||||
|
]));
|
||||||
|
ui_spans.push(current_session_indication);
|
||||||
|
}
|
||||||
|
ui_spans
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_tab_ui_line(tab_ui_info: &TabUiInfo, colors: Colors) -> Vec<UiSpan> {
|
||||||
|
let mut ui_spans = vec![];
|
||||||
|
let tab_name = &tab_ui_info.name;
|
||||||
|
let pane_count_text = tab_ui_info.panes.len();
|
||||||
|
let pane_count = format!("{}", pane_count_text);
|
||||||
|
let pane_count_styled = colors.green(&pane_count);
|
||||||
|
let tab_bullet_span =
|
||||||
|
UiSpan::UiSpanTelescope(UiSpanTelescope::new(vec![StringAndLength::new(
|
||||||
|
format!(" - "),
|
||||||
|
4,
|
||||||
|
)]));
|
||||||
|
let tab_name_span = UiSpan::TruncatableUiSpan(TruncatableUiSpan::new(
|
||||||
|
tab_name.clone(),
|
||||||
|
SpanStyle::ForegroundBold(colors.palette.cyan),
|
||||||
|
));
|
||||||
|
let connected_users_count_span = UiSpan::UiSpanTelescope(UiSpanTelescope::new(vec![
|
||||||
|
StringAndLength::new(
|
||||||
|
format!(" ({pane_count_styled} panes)"),
|
||||||
|
2 + pane_count.width() + 7,
|
||||||
|
),
|
||||||
|
StringAndLength::new(
|
||||||
|
format!(" ({pane_count_styled})"),
|
||||||
|
2 + pane_count.width() + 1,
|
||||||
|
),
|
||||||
|
]));
|
||||||
|
ui_spans.push(tab_bullet_span);
|
||||||
|
ui_spans.push(tab_name_span);
|
||||||
|
ui_spans.push(connected_users_count_span);
|
||||||
|
ui_spans
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_pane_ui_line(pane_ui_info: &PaneUiInfo, colors: Colors) -> Vec<UiSpan> {
|
||||||
|
let mut ui_spans = vec![];
|
||||||
|
let pane_name = pane_ui_info.name.clone();
|
||||||
|
let exit_code = pane_ui_info.exit_code.map(|exit_code_number| {
|
||||||
|
let exit_code = format!("{}", exit_code_number);
|
||||||
|
let exit_code = if exit_code_number == 0 {
|
||||||
|
colors.green(&exit_code)
|
||||||
|
} else {
|
||||||
|
colors.red(&exit_code)
|
||||||
|
};
|
||||||
|
exit_code
|
||||||
|
});
|
||||||
|
let pane_bullet_span =
|
||||||
|
UiSpan::UiSpanTelescope(UiSpanTelescope::new(vec![StringAndLength::new(
|
||||||
|
format!(" > "),
|
||||||
|
6,
|
||||||
|
)]));
|
||||||
|
ui_spans.push(pane_bullet_span);
|
||||||
|
let pane_name_span =
|
||||||
|
UiSpan::TruncatableUiSpan(TruncatableUiSpan::new(pane_name, SpanStyle::Bold));
|
||||||
|
ui_spans.push(pane_name_span);
|
||||||
|
if let Some(exit_code) = exit_code {
|
||||||
|
let pane_name_span = UiSpan::UiSpanTelescope(UiSpanTelescope::new(vec![
|
||||||
|
StringAndLength::new(
|
||||||
|
format!(" (EXIT CODE: {exit_code})"),
|
||||||
|
13 + exit_code.width() + 1,
|
||||||
|
),
|
||||||
|
StringAndLength::new(format!(" ({exit_code})"), 2 + exit_code.width() + 1),
|
||||||
|
]));
|
||||||
|
ui_spans.push(pane_name_span);
|
||||||
|
}
|
||||||
|
ui_spans
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn minimize_lines(
|
||||||
|
total_count: usize,
|
||||||
|
line_count_to_remove: usize,
|
||||||
|
selected_index: Option<usize>,
|
||||||
|
) -> (usize, usize, usize, usize) {
|
||||||
|
// returns: (start_index, anchor_index, end_index, lines_left_to_remove)
|
||||||
|
let (count_to_render, line_count_to_remove) = if line_count_to_remove > total_count {
|
||||||
|
(1, line_count_to_remove.saturating_sub(total_count) + 1)
|
||||||
|
} else {
|
||||||
|
(total_count.saturating_sub(line_count_to_remove), 0)
|
||||||
|
};
|
||||||
|
let anchor_index = selected_index.unwrap_or(0); // 5
|
||||||
|
let mut start_index = anchor_index.saturating_sub(count_to_render / 2);
|
||||||
|
let mut end_index = start_index + count_to_render;
|
||||||
|
if end_index > total_count {
|
||||||
|
start_index = start_index.saturating_sub(end_index - total_count);
|
||||||
|
end_index = total_count;
|
||||||
|
}
|
||||||
|
(start_index, anchor_index, end_index, line_count_to_remove)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_prompt(typing_session_name: bool, search_term: &str, colors: Colors) {
|
||||||
|
if !typing_session_name {
|
||||||
|
let prompt = colors.bold(&format!("> {}_", search_term));
|
||||||
|
println!("{}\n", prompt);
|
||||||
|
} else {
|
||||||
|
println!("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_new_session_line(session_name: &Option<String>, is_searching: bool, colors: Colors) {
|
||||||
|
if is_searching {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let new_session_shortcut_text = "<Ctrl w>";
|
||||||
|
let new_session_shortcut = colors.magenta(new_session_shortcut_text);
|
||||||
|
let new_session = colors.bold("New session");
|
||||||
|
let enter = colors.magenta("<ENTER>");
|
||||||
|
match session_name {
|
||||||
|
Some(session_name) => {
|
||||||
|
println!(
|
||||||
|
"\u{1b}[m > {}_ ({}, {} when done)",
|
||||||
|
colors.orange(session_name),
|
||||||
|
colors.bold("Type optional name"),
|
||||||
|
enter
|
||||||
|
);
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
println!("\u{1b}[m > {new_session_shortcut} - {new_session}");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_controls_line(is_searching: bool, row: usize, max_cols: usize, colors: Colors) {
|
||||||
|
let (arrows, navigate) = if is_searching {
|
||||||
|
(colors.magenta("<↓↑>"), colors.bold("Navigate"))
|
||||||
|
} else {
|
||||||
|
(colors.magenta("<←↓↑→>"), colors.bold("Navigate and Expand"))
|
||||||
|
};
|
||||||
|
let enter = colors.magenta("<ENTER>");
|
||||||
|
let select = colors.bold("Switch to selected");
|
||||||
|
let esc = colors.magenta("<ESC>");
|
||||||
|
let to_hide = colors.bold("Hide");
|
||||||
|
|
||||||
|
if max_cols >= 80 {
|
||||||
|
print!(
|
||||||
|
"\u{1b}[m\u{1b}[{row}HHelp: {arrows} - {navigate}, {enter} - {select}, {esc} - {to_hide}"
|
||||||
|
);
|
||||||
|
} else if max_cols >= 57 {
|
||||||
|
let navigate = colors.bold("Navigate");
|
||||||
|
let select = colors.bold("Switch");
|
||||||
|
print!(
|
||||||
|
"\u{1b}[m\u{1b}[{row}HHelp: {arrows} - {navigate}, {enter} - {select}, {esc} - {to_hide}"
|
||||||
|
);
|
||||||
|
} else if max_cols >= 20 {
|
||||||
|
print!("\u{1b}[m\u{1b}[{row}H{arrows}/{enter}/{esc}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, Copy)]
|
||||||
|
pub struct Colors {
|
||||||
|
pub palette: Palette,
|
||||||
|
}
|
||||||
|
impl Colors {
|
||||||
|
pub fn new(palette: Palette) -> Self {
|
||||||
|
Colors { palette }
|
||||||
|
}
|
||||||
|
pub fn bold(&self, text: &str) -> String {
|
||||||
|
format!("\u{1b}[1m{}\u{1b}[22m", text)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn color(&self, color: &PaletteColor, text: &str) -> String {
|
||||||
|
match color {
|
||||||
|
PaletteColor::EightBit(byte) => {
|
||||||
|
format!("\u{1b}[38;5;{};1m{}\u{1b}[39;22m", byte, text)
|
||||||
|
},
|
||||||
|
PaletteColor::Rgb((r, g, b)) => {
|
||||||
|
format!("\u{1b}[38;2;{};{};{};1m{}\u{1b}[39;22m", r, g, b, text)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn orange(&self, text: &str) -> String {
|
||||||
|
self.color(&self.palette.orange, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn green(&self, text: &str) -> String {
|
||||||
|
self.color(&self.palette.green, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn red(&self, text: &str) -> String {
|
||||||
|
self.color(&self.palette.red, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cyan(&self, text: &str) -> String {
|
||||||
|
self.color(&self.palette.cyan, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn magenta(&self, text: &str) -> String {
|
||||||
|
self.color(&self.palette.magenta, text)
|
||||||
|
}
|
||||||
|
}
|
||||||
357
default-plugins/session-manager/src/ui/mod.rs
Normal file
357
default-plugins/session-manager/src/ui/mod.rs
Normal file
|
|
@ -0,0 +1,357 @@
|
||||||
|
pub mod components;
|
||||||
|
use zellij_tile::prelude::*;
|
||||||
|
|
||||||
|
use crate::session_list::{SelectedIndex, SessionList};
|
||||||
|
use components::{
|
||||||
|
build_pane_ui_line, build_session_ui_line, build_tab_ui_line, minimize_lines, Colors,
|
||||||
|
LineToRender,
|
||||||
|
};
|
||||||
|
|
||||||
|
macro_rules! render_assets {
|
||||||
|
($assets:expr, $line_count_to_remove:expr, $selected_index:expr, $to_render_until_selected: expr, $to_render_after_selected:expr, $has_deeper_selected_assets:expr, $max_cols:expr, $colors:expr) => {{
|
||||||
|
let (start_index, anchor_asset_index, end_index, line_count_to_remove) =
|
||||||
|
minimize_lines($assets.len(), $line_count_to_remove, $selected_index);
|
||||||
|
let mut truncated_result_count_above = start_index;
|
||||||
|
let mut truncated_result_count_below = $assets.len().saturating_sub(end_index);
|
||||||
|
let mut current_index = 1;
|
||||||
|
if let Some(assets_to_render_before_selected) = $assets.get(start_index..anchor_asset_index)
|
||||||
|
{
|
||||||
|
for asset in assets_to_render_before_selected {
|
||||||
|
let mut asset: LineToRender =
|
||||||
|
asset.as_line_to_render(current_index, $max_cols, $colors);
|
||||||
|
asset.add_truncated_results(truncated_result_count_above);
|
||||||
|
truncated_result_count_above = 0;
|
||||||
|
current_index += 1;
|
||||||
|
$to_render_until_selected.push(asset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(selected_asset) = $assets.get(anchor_asset_index) {
|
||||||
|
if $selected_index.is_some() && !$has_deeper_selected_assets {
|
||||||
|
let mut selected_asset: LineToRender =
|
||||||
|
selected_asset.as_line_to_render(current_index, $max_cols, $colors);
|
||||||
|
selected_asset.make_selected();
|
||||||
|
selected_asset.add_truncated_results(truncated_result_count_above);
|
||||||
|
if anchor_asset_index + 1 >= end_index {
|
||||||
|
// no more results below, let's add the more indication if we need to
|
||||||
|
selected_asset.add_truncated_results(truncated_result_count_below);
|
||||||
|
}
|
||||||
|
current_index += 1;
|
||||||
|
$to_render_until_selected.push(selected_asset);
|
||||||
|
} else {
|
||||||
|
$to_render_until_selected.push(selected_asset.as_line_to_render(
|
||||||
|
current_index,
|
||||||
|
$max_cols,
|
||||||
|
$colors,
|
||||||
|
));
|
||||||
|
current_index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(assets_to_render_after_selected) =
|
||||||
|
$assets.get(anchor_asset_index + 1..end_index)
|
||||||
|
{
|
||||||
|
for asset in assets_to_render_after_selected.iter().rev() {
|
||||||
|
let mut asset: LineToRender =
|
||||||
|
asset.as_line_to_render(current_index, $max_cols, $colors);
|
||||||
|
asset.add_truncated_results(truncated_result_count_below);
|
||||||
|
truncated_result_count_below = 0;
|
||||||
|
current_index += 1;
|
||||||
|
$to_render_after_selected.insert(0, asset.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
line_count_to_remove
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SessionList {
|
||||||
|
pub fn render(&self, max_rows: usize, max_cols: usize, colors: Colors) -> Vec<LineToRender> {
|
||||||
|
if self.is_searching {
|
||||||
|
self.render_search_results(max_rows, max_cols)
|
||||||
|
} else {
|
||||||
|
self.render_list(max_rows, max_cols, colors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn render_search_results(&self, max_rows: usize, max_cols: usize) -> Vec<LineToRender> {
|
||||||
|
let mut lines_to_render = vec![];
|
||||||
|
for (i, result) in self.search_results.iter().enumerate() {
|
||||||
|
if lines_to_render.len() + result.lines_to_render() <= max_rows {
|
||||||
|
let mut result_lines = result.render(max_cols);
|
||||||
|
if Some(i) == self.selected_search_index {
|
||||||
|
for line_to_render in result_lines.iter_mut() {
|
||||||
|
line_to_render.make_selected();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lines_to_render.append(&mut result_lines);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lines_to_render
|
||||||
|
}
|
||||||
|
fn render_list(&self, max_rows: usize, max_cols: usize, colors: Colors) -> Vec<LineToRender> {
|
||||||
|
let mut lines_to_render_until_selected = vec![];
|
||||||
|
let mut lines_to_render_after_selected = vec![];
|
||||||
|
let total_lines_to_render = self.total_lines_to_render();
|
||||||
|
let line_count_to_remove = total_lines_to_render.saturating_sub(max_rows);
|
||||||
|
let line_count_to_remove = self.render_sessions(
|
||||||
|
&mut lines_to_render_until_selected,
|
||||||
|
&mut lines_to_render_after_selected,
|
||||||
|
line_count_to_remove,
|
||||||
|
max_cols,
|
||||||
|
colors,
|
||||||
|
);
|
||||||
|
let line_count_to_remove = self.render_tabs(
|
||||||
|
&mut lines_to_render_until_selected,
|
||||||
|
&mut lines_to_render_after_selected,
|
||||||
|
line_count_to_remove,
|
||||||
|
max_cols,
|
||||||
|
colors,
|
||||||
|
);
|
||||||
|
self.render_panes(
|
||||||
|
&mut lines_to_render_until_selected,
|
||||||
|
&mut lines_to_render_after_selected,
|
||||||
|
line_count_to_remove,
|
||||||
|
max_cols,
|
||||||
|
colors,
|
||||||
|
);
|
||||||
|
let mut lines_to_render = lines_to_render_until_selected;
|
||||||
|
lines_to_render.append(&mut lines_to_render_after_selected);
|
||||||
|
lines_to_render
|
||||||
|
}
|
||||||
|
fn render_sessions(
|
||||||
|
&self,
|
||||||
|
to_render_until_selected: &mut Vec<LineToRender>,
|
||||||
|
to_render_after_selected: &mut Vec<LineToRender>,
|
||||||
|
line_count_to_remove: usize,
|
||||||
|
max_cols: usize,
|
||||||
|
colors: Colors,
|
||||||
|
) -> usize {
|
||||||
|
render_assets!(
|
||||||
|
self.session_ui_infos,
|
||||||
|
line_count_to_remove,
|
||||||
|
self.selected_index.0,
|
||||||
|
to_render_until_selected,
|
||||||
|
to_render_after_selected,
|
||||||
|
self.selected_index.1.is_some(),
|
||||||
|
max_cols,
|
||||||
|
colors
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn render_tabs(
|
||||||
|
&self,
|
||||||
|
to_render_until_selected: &mut Vec<LineToRender>,
|
||||||
|
to_render_after_selected: &mut Vec<LineToRender>,
|
||||||
|
line_count_to_remove: usize,
|
||||||
|
max_cols: usize,
|
||||||
|
colors: Colors,
|
||||||
|
) -> usize {
|
||||||
|
if self.selected_index.1.is_none() {
|
||||||
|
return line_count_to_remove;
|
||||||
|
}
|
||||||
|
if let Some(tabs_in_session) = self
|
||||||
|
.selected_index
|
||||||
|
.0
|
||||||
|
.and_then(|i| self.session_ui_infos.get(i))
|
||||||
|
.map(|s| &s.tabs)
|
||||||
|
{
|
||||||
|
render_assets!(
|
||||||
|
tabs_in_session,
|
||||||
|
line_count_to_remove,
|
||||||
|
self.selected_index.1,
|
||||||
|
to_render_until_selected,
|
||||||
|
to_render_after_selected,
|
||||||
|
self.selected_index.2.is_some(),
|
||||||
|
max_cols,
|
||||||
|
colors
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
line_count_to_remove
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn render_panes(
|
||||||
|
&self,
|
||||||
|
to_render_until_selected: &mut Vec<LineToRender>,
|
||||||
|
to_render_after_selected: &mut Vec<LineToRender>,
|
||||||
|
line_count_to_remove: usize,
|
||||||
|
max_cols: usize,
|
||||||
|
colors: Colors,
|
||||||
|
) -> usize {
|
||||||
|
if self.selected_index.2.is_none() {
|
||||||
|
return line_count_to_remove;
|
||||||
|
}
|
||||||
|
if let Some(panes_in_session) = self
|
||||||
|
.selected_index
|
||||||
|
.0
|
||||||
|
.and_then(|i| self.session_ui_infos.get(i))
|
||||||
|
.map(|s| &s.tabs)
|
||||||
|
.and_then(|tabs| {
|
||||||
|
self.selected_index
|
||||||
|
.1
|
||||||
|
.and_then(|i| tabs.get(i))
|
||||||
|
.map(|t| &t.panes)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
render_assets!(
|
||||||
|
panes_in_session,
|
||||||
|
line_count_to_remove,
|
||||||
|
self.selected_index.2,
|
||||||
|
to_render_until_selected,
|
||||||
|
to_render_after_selected,
|
||||||
|
false,
|
||||||
|
max_cols,
|
||||||
|
colors
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
line_count_to_remove
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn total_lines_to_render(&self) -> usize {
|
||||||
|
self.session_ui_infos
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.fold(0, |acc, (index, s)| {
|
||||||
|
if self.selected_index.session_index_is_selected(index) {
|
||||||
|
acc + s.line_count(&self.selected_index)
|
||||||
|
} else {
|
||||||
|
acc + 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SessionUiInfo {
|
||||||
|
pub name: String,
|
||||||
|
pub tabs: Vec<TabUiInfo>,
|
||||||
|
pub connected_users: usize,
|
||||||
|
pub is_current_session: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SessionUiInfo {
|
||||||
|
pub fn from_session_info(session_info: &SessionInfo) -> Self {
|
||||||
|
SessionUiInfo {
|
||||||
|
name: session_info.name.clone(),
|
||||||
|
tabs: session_info
|
||||||
|
.tabs
|
||||||
|
.iter()
|
||||||
|
.map(|t| TabUiInfo::new(t, &session_info.panes))
|
||||||
|
.collect(),
|
||||||
|
connected_users: session_info.connected_clients,
|
||||||
|
is_current_session: session_info.is_current_session,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn line_count(&self, selected_index: &SelectedIndex) -> usize {
|
||||||
|
let mut line_count = 1; // self
|
||||||
|
if selected_index.tabs_are_visible() {
|
||||||
|
match selected_index
|
||||||
|
.selected_tab_index()
|
||||||
|
.and_then(|i| self.tabs.get(i))
|
||||||
|
.map(|t| t.line_count(&selected_index))
|
||||||
|
{
|
||||||
|
Some(line_count_of_selected_tab) => {
|
||||||
|
// we add the line count in the selected tab minus 1 because we will account
|
||||||
|
// for the selected tab line itself in self.tabs.len() below
|
||||||
|
line_count += line_count_of_selected_tab.saturating_sub(1);
|
||||||
|
line_count += self.tabs.len();
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
line_count += self.tabs.len();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
line_count
|
||||||
|
}
|
||||||
|
fn as_line_to_render(
|
||||||
|
&self,
|
||||||
|
_session_index: u8,
|
||||||
|
mut max_cols: usize,
|
||||||
|
colors: Colors,
|
||||||
|
) -> LineToRender {
|
||||||
|
let mut line_to_render = LineToRender::new(colors);
|
||||||
|
let ui_spans = build_session_ui_line(&self, colors);
|
||||||
|
for span in ui_spans {
|
||||||
|
span.render(None, &mut line_to_render, &mut max_cols);
|
||||||
|
}
|
||||||
|
line_to_render
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TabUiInfo {
|
||||||
|
pub name: String,
|
||||||
|
pub panes: Vec<PaneUiInfo>,
|
||||||
|
pub position: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TabUiInfo {
|
||||||
|
pub fn new(tab_info: &TabInfo, pane_manifest: &PaneManifest) -> Self {
|
||||||
|
let panes = pane_manifest
|
||||||
|
.panes
|
||||||
|
.get(&tab_info.position)
|
||||||
|
.map(|p| {
|
||||||
|
p.iter()
|
||||||
|
.filter_map(|pane_info| {
|
||||||
|
if pane_info.is_selectable {
|
||||||
|
Some(PaneUiInfo {
|
||||||
|
name: pane_info.title.clone(),
|
||||||
|
exit_code: pane_info.exit_status.clone(),
|
||||||
|
pane_id: pane_info.id,
|
||||||
|
is_plugin: pane_info.is_plugin,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
TabUiInfo {
|
||||||
|
name: tab_info.name.clone(),
|
||||||
|
panes,
|
||||||
|
position: tab_info.position,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn line_count(&self, selected_index: &SelectedIndex) -> usize {
|
||||||
|
let mut line_count = 1; // self
|
||||||
|
if selected_index.panes_are_visible() {
|
||||||
|
line_count += self.panes.len()
|
||||||
|
}
|
||||||
|
line_count
|
||||||
|
}
|
||||||
|
fn as_line_to_render(
|
||||||
|
&self,
|
||||||
|
_session_index: u8,
|
||||||
|
mut max_cols: usize,
|
||||||
|
colors: Colors,
|
||||||
|
) -> LineToRender {
|
||||||
|
let mut line_to_render = LineToRender::new(colors);
|
||||||
|
let ui_spans = build_tab_ui_line(&self, colors);
|
||||||
|
for span in ui_spans {
|
||||||
|
span.render(None, &mut line_to_render, &mut max_cols);
|
||||||
|
}
|
||||||
|
line_to_render
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct PaneUiInfo {
|
||||||
|
pub name: String,
|
||||||
|
pub exit_code: Option<i32>,
|
||||||
|
pub pane_id: u32,
|
||||||
|
pub is_plugin: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PaneUiInfo {
|
||||||
|
fn as_line_to_render(
|
||||||
|
&self,
|
||||||
|
_session_index: u8,
|
||||||
|
mut max_cols: usize,
|
||||||
|
colors: Colors,
|
||||||
|
) -> LineToRender {
|
||||||
|
let mut line_to_render = LineToRender::new(colors);
|
||||||
|
let ui_spans = build_pane_ui_line(&self, colors);
|
||||||
|
for span in ui_spans {
|
||||||
|
span.render(None, &mut line_to_render, &mut max_cols);
|
||||||
|
}
|
||||||
|
line_to_render
|
||||||
|
}
|
||||||
|
}
|
||||||
268
src/commands.rs
268
src/commands.rs
|
|
@ -16,6 +16,7 @@ use zellij_client::{
|
||||||
use zellij_server::{os_input_output::get_server_os_input, start_server as start_server_impl};
|
use zellij_server::{os_input_output::get_server_os_input, start_server as start_server_impl};
|
||||||
use zellij_utils::{
|
use zellij_utils::{
|
||||||
cli::{CliArgs, Command, SessionCommand, Sessions},
|
cli::{CliArgs, Command, SessionCommand, Sessions},
|
||||||
|
data::ConnectToSession,
|
||||||
envs,
|
envs,
|
||||||
input::{
|
input::{
|
||||||
actions::Action,
|
actions::Action,
|
||||||
|
|
@ -331,131 +332,180 @@ pub(crate) fn start_client(opts: CliArgs) {
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
let mut reconnect_to_session: Option<ConnectToSession> = None;
|
||||||
let os_input = get_os_input(get_client_os_input);
|
let os_input = get_os_input(get_client_os_input);
|
||||||
|
loop {
|
||||||
|
let os_input = os_input.clone();
|
||||||
|
let config = config.clone();
|
||||||
|
let layout = layout.clone();
|
||||||
|
let mut config_options = config_options.clone();
|
||||||
|
let mut opts = opts.clone();
|
||||||
|
|
||||||
let start_client_plan = |session_name: std::string::String| {
|
if let Some(reconnect_to_session) = &reconnect_to_session {
|
||||||
assert_session_ne(&session_name);
|
// this is integration code to make session reconnects work with this existing,
|
||||||
};
|
// untested and pretty involved function
|
||||||
|
//
|
||||||
if let Some(Command::Sessions(Sessions::Attach {
|
// ideally, we should write tests for this whole function and refctor it
|
||||||
session_name,
|
if reconnect_to_session.name.is_some() {
|
||||||
create,
|
opts.command = Some(Command::Sessions(Sessions::Attach {
|
||||||
index,
|
session_name: reconnect_to_session.name.clone(),
|
||||||
options,
|
create: true,
|
||||||
})) = opts.command.clone()
|
index: None,
|
||||||
{
|
options: None,
|
||||||
let config_options = match options.as_deref() {
|
}));
|
||||||
Some(SessionCommand::Options(o)) => config_options.merge_from_cli(o.to_owned().into()),
|
} else {
|
||||||
None => config_options,
|
opts.command = None;
|
||||||
};
|
opts.session = None;
|
||||||
|
config_options.attach_to_session = None;
|
||||||
let client = if let Some(idx) = index {
|
|
||||||
attach_with_session_index(config_options.clone(), idx, create)
|
|
||||||
} else {
|
|
||||||
let session_exists = session_name
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|s| session_exists(&s).ok())
|
|
||||||
.unwrap_or(false);
|
|
||||||
if create && !session_exists {
|
|
||||||
session_name.clone().map(start_client_plan);
|
|
||||||
}
|
|
||||||
attach_with_session_name(session_name, config_options.clone(), create)
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Ok(val) = std::env::var(envs::SESSION_NAME_ENV_KEY) {
|
|
||||||
if val == *client.get_session_name() {
|
|
||||||
eprintln!("You are trying to attach to the current session (\"{}\"). Zellij does not support nesting a session in itself.", val);
|
|
||||||
process::exit(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let attach_layout = match client {
|
let start_client_plan = |session_name: std::string::String| {
|
||||||
ClientInfo::Attach(_, _) => None,
|
assert_session_ne(&session_name);
|
||||||
ClientInfo::New(_) => Some(layout),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
start_client_impl(
|
if let Some(Command::Sessions(Sessions::Attach {
|
||||||
Box::new(os_input),
|
session_name,
|
||||||
opts,
|
create,
|
||||||
config,
|
index,
|
||||||
config_options,
|
options,
|
||||||
client,
|
})) = opts.command.clone()
|
||||||
attach_layout,
|
{
|
||||||
);
|
let config_options = match options.as_deref() {
|
||||||
} else {
|
Some(SessionCommand::Options(o)) => {
|
||||||
if let Some(session_name) = opts.session.clone() {
|
config_options.merge_from_cli(o.to_owned().into())
|
||||||
start_client_plan(session_name.clone());
|
},
|
||||||
start_client_impl(
|
None => config_options,
|
||||||
Box::new(os_input),
|
};
|
||||||
opts,
|
|
||||||
config,
|
let client = if let Some(idx) = index {
|
||||||
config_options,
|
attach_with_session_index(config_options.clone(), idx, create)
|
||||||
ClientInfo::New(session_name),
|
} else {
|
||||||
Some(layout),
|
let session_exists = session_name
|
||||||
);
|
.as_ref()
|
||||||
} else {
|
.and_then(|s| session_exists(&s).ok())
|
||||||
if let Some(session_name) = config_options.session_name.as_ref() {
|
.unwrap_or(false);
|
||||||
if let Ok(val) = envs::get_session_name() {
|
if create && !session_exists {
|
||||||
// This prevents the same type of recursion as above, only that here we
|
session_name.clone().map(start_client_plan);
|
||||||
// don't get the command to "attach", but to start a new session instead.
|
|
||||||
// This occurs for example when declaring the session name inside a layout
|
|
||||||
// file and then, from within this session, trying to open a new zellij
|
|
||||||
// session with the same layout. This causes an infinite recursion in the
|
|
||||||
// `zellij_server::terminal_bytes::listen` task, flooding the server and
|
|
||||||
// clients with infinite `Render` requests.
|
|
||||||
if *session_name == val {
|
|
||||||
eprintln!("You are trying to attach to the current session (\"{}\"). Zellij does not support nesting a session in itself.", session_name);
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
match config_options.attach_to_session {
|
attach_with_session_name(session_name, config_options.clone(), create)
|
||||||
Some(true) => {
|
};
|
||||||
let client = attach_with_session_name(
|
|
||||||
Some(session_name.clone()),
|
if let Ok(val) = std::env::var(envs::SESSION_NAME_ENV_KEY) {
|
||||||
config_options.clone(),
|
if val == *client.get_session_name() {
|
||||||
true,
|
panic!("You are trying to attach to the current session (\"{}\"). This is not supported.", val);
|
||||||
);
|
|
||||||
let attach_layout = match client {
|
|
||||||
ClientInfo::Attach(_, _) => None,
|
|
||||||
ClientInfo::New(_) => Some(layout),
|
|
||||||
};
|
|
||||||
start_client_impl(
|
|
||||||
Box::new(os_input),
|
|
||||||
opts,
|
|
||||||
config,
|
|
||||||
config_options,
|
|
||||||
client,
|
|
||||||
attach_layout,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
start_client_plan(session_name.clone());
|
|
||||||
start_client_impl(
|
|
||||||
Box::new(os_input),
|
|
||||||
opts,
|
|
||||||
config,
|
|
||||||
config_options.clone(),
|
|
||||||
ClientInfo::New(session_name.clone()),
|
|
||||||
Some(layout),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
// after we detach, this happens and so we need to exit before the rest of the
|
|
||||||
// function happens
|
|
||||||
// TODO: offload this to a different function
|
|
||||||
process::exit(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let session_name = generate_unique_session_name();
|
let attach_layout = match client {
|
||||||
start_client_plan(session_name.clone());
|
ClientInfo::Attach(_, _) => None,
|
||||||
start_client_impl(
|
ClientInfo::New(_) => Some(layout),
|
||||||
|
};
|
||||||
|
|
||||||
|
let tab_position_to_focus = reconnect_to_session
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|r| r.tab_position.clone());
|
||||||
|
let pane_id_to_focus = reconnect_to_session
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|r| r.pane_id.clone());
|
||||||
|
reconnect_to_session = start_client_impl(
|
||||||
Box::new(os_input),
|
Box::new(os_input),
|
||||||
opts,
|
opts,
|
||||||
config,
|
config,
|
||||||
config_options,
|
config_options,
|
||||||
ClientInfo::New(session_name),
|
client,
|
||||||
Some(layout),
|
attach_layout,
|
||||||
|
tab_position_to_focus,
|
||||||
|
pane_id_to_focus,
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
if let Some(session_name) = opts.session.clone() {
|
||||||
|
start_client_plan(session_name.clone());
|
||||||
|
reconnect_to_session = start_client_impl(
|
||||||
|
Box::new(os_input),
|
||||||
|
opts,
|
||||||
|
config,
|
||||||
|
config_options,
|
||||||
|
ClientInfo::New(session_name),
|
||||||
|
Some(layout),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if let Some(session_name) = config_options.session_name.as_ref() {
|
||||||
|
if let Ok(val) = envs::get_session_name() {
|
||||||
|
// This prevents the same type of recursion as above, only that here we
|
||||||
|
// don't get the command to "attach", but to start a new session instead.
|
||||||
|
// This occurs for example when declaring the session name inside a layout
|
||||||
|
// file and then, from within this session, trying to open a new zellij
|
||||||
|
// session with the same layout. This causes an infinite recursion in the
|
||||||
|
// `zellij_server::terminal_bytes::listen` task, flooding the server and
|
||||||
|
// clients with infinite `Render` requests.
|
||||||
|
if *session_name == val {
|
||||||
|
eprintln!("You are trying to attach to the current session (\"{}\"). Zellij does not support nesting a session in itself.", session_name);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match config_options.attach_to_session {
|
||||||
|
Some(true) => {
|
||||||
|
let client = attach_with_session_name(
|
||||||
|
Some(session_name.clone()),
|
||||||
|
config_options.clone(),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
let attach_layout = match client {
|
||||||
|
ClientInfo::Attach(_, _) => None,
|
||||||
|
ClientInfo::New(_) => Some(layout),
|
||||||
|
};
|
||||||
|
reconnect_to_session = start_client_impl(
|
||||||
|
Box::new(os_input),
|
||||||
|
opts,
|
||||||
|
config,
|
||||||
|
config_options,
|
||||||
|
client,
|
||||||
|
attach_layout,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
start_client_plan(session_name.clone());
|
||||||
|
reconnect_to_session = start_client_impl(
|
||||||
|
Box::new(os_input),
|
||||||
|
opts,
|
||||||
|
config,
|
||||||
|
config_options.clone(),
|
||||||
|
ClientInfo::New(session_name.clone()),
|
||||||
|
Some(layout),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if reconnect_to_session.is_some() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// after we detach, this happens and so we need to exit before the rest of the
|
||||||
|
// function happens
|
||||||
|
process::exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let session_name = generate_unique_session_name();
|
||||||
|
start_client_plan(session_name.clone());
|
||||||
|
reconnect_to_session = start_client_impl(
|
||||||
|
Box::new(os_input),
|
||||||
|
opts,
|
||||||
|
config,
|
||||||
|
config_options,
|
||||||
|
ClientInfo::New(session_name),
|
||||||
|
Some(layout),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if reconnect_to_session.is_none() {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ lazy_static::lazy_static! {
|
||||||
WorkspaceMember{crate_name: "default-plugins/strider", build: true},
|
WorkspaceMember{crate_name: "default-plugins/strider", build: true},
|
||||||
WorkspaceMember{crate_name: "default-plugins/tab-bar", build: true},
|
WorkspaceMember{crate_name: "default-plugins/tab-bar", build: true},
|
||||||
WorkspaceMember{crate_name: "default-plugins/fixture-plugin-for-tests", build: true},
|
WorkspaceMember{crate_name: "default-plugins/fixture-plugin-for-tests", build: true},
|
||||||
|
WorkspaceMember{crate_name: "default-plugins/session-manager", build: true},
|
||||||
WorkspaceMember{crate_name: "zellij-utils", build: false},
|
WorkspaceMember{crate_name: "zellij-utils", build: false},
|
||||||
WorkspaceMember{crate_name: "zellij-tile-utils", build: false},
|
WorkspaceMember{crate_name: "zellij-tile-utils", build: false},
|
||||||
WorkspaceMember{crate_name: "zellij-tile", build: false},
|
WorkspaceMember{crate_name: "zellij-tile", build: false},
|
||||||
|
|
|
||||||
|
|
@ -157,6 +157,9 @@ impl InputHandler {
|
||||||
.send(ClientInstruction::DoneParsingStdinQuery)
|
.send(ClientInstruction::DoneParsingStdinQuery)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
},
|
},
|
||||||
|
Ok((InputInstruction::Exit, _error_context)) => {
|
||||||
|
self.should_exit = true;
|
||||||
|
},
|
||||||
Err(err) => panic!("Encountered read error: {:?}", err),
|
Err(err) => panic!("Encountered read error: {:?}", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ use crate::{
|
||||||
};
|
};
|
||||||
use zellij_utils::{
|
use zellij_utils::{
|
||||||
channels::{self, ChannelWithContext, SenderWithContext},
|
channels::{self, ChannelWithContext, SenderWithContext},
|
||||||
consts::ZELLIJ_IPC_PIPE,
|
consts::{set_permissions, ZELLIJ_SOCK_DIR},
|
||||||
data::{ClientId, InputMode, Style},
|
data::{ClientId, ConnectToSession, InputMode, Style},
|
||||||
envs,
|
envs,
|
||||||
errors::{ClientContext, ContextType, ErrorInstruction},
|
errors::{ClientContext, ContextType, ErrorInstruction},
|
||||||
input::{config::Config, options::Options},
|
input::{config::Config, options::Options},
|
||||||
|
|
@ -46,6 +46,7 @@ pub(crate) enum ClientInstruction {
|
||||||
StartedParsingStdinQuery,
|
StartedParsingStdinQuery,
|
||||||
DoneParsingStdinQuery,
|
DoneParsingStdinQuery,
|
||||||
Log(Vec<String>),
|
Log(Vec<String>),
|
||||||
|
SwitchSession(ConnectToSession),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ServerToClientMsg> for ClientInstruction {
|
impl From<ServerToClientMsg> for ClientInstruction {
|
||||||
|
|
@ -60,6 +61,9 @@ impl From<ServerToClientMsg> for ClientInstruction {
|
||||||
ServerToClientMsg::Connected => ClientInstruction::Connected,
|
ServerToClientMsg::Connected => ClientInstruction::Connected,
|
||||||
ServerToClientMsg::ActiveClients(clients) => ClientInstruction::ActiveClients(clients),
|
ServerToClientMsg::ActiveClients(clients) => ClientInstruction::ActiveClients(clients),
|
||||||
ServerToClientMsg::Log(log_lines) => ClientInstruction::Log(log_lines),
|
ServerToClientMsg::Log(log_lines) => ClientInstruction::Log(log_lines),
|
||||||
|
ServerToClientMsg::SwitchSession(connect_to_session) => {
|
||||||
|
ClientInstruction::SwitchSession(connect_to_session)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -77,6 +81,7 @@ impl From<&ClientInstruction> for ClientContext {
|
||||||
ClientInstruction::Log(_) => ClientContext::Log,
|
ClientInstruction::Log(_) => ClientContext::Log,
|
||||||
ClientInstruction::StartedParsingStdinQuery => ClientContext::StartedParsingStdinQuery,
|
ClientInstruction::StartedParsingStdinQuery => ClientContext::StartedParsingStdinQuery,
|
||||||
ClientInstruction::DoneParsingStdinQuery => ClientContext::DoneParsingStdinQuery,
|
ClientInstruction::DoneParsingStdinQuery => ClientContext::DoneParsingStdinQuery,
|
||||||
|
ClientInstruction::SwitchSession(..) => ClientContext::SwitchSession,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -130,6 +135,7 @@ pub(crate) enum InputInstruction {
|
||||||
AnsiStdinInstructions(Vec<AnsiStdinInstruction>),
|
AnsiStdinInstructions(Vec<AnsiStdinInstruction>),
|
||||||
StartedParsing,
|
StartedParsing,
|
||||||
DoneParsing,
|
DoneParsing,
|
||||||
|
Exit,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_client(
|
pub fn start_client(
|
||||||
|
|
@ -139,8 +145,12 @@ pub fn start_client(
|
||||||
config_options: Options,
|
config_options: Options,
|
||||||
info: ClientInfo,
|
info: ClientInfo,
|
||||||
layout: Option<Layout>,
|
layout: Option<Layout>,
|
||||||
) {
|
tab_position_to_focus: Option<usize>,
|
||||||
|
pane_id_to_focus: Option<(u32, bool)>, // (pane_id, is_plugin)
|
||||||
|
) -> Option<ConnectToSession> {
|
||||||
info!("Starting Zellij client!");
|
info!("Starting Zellij client!");
|
||||||
|
|
||||||
|
let mut reconnect_to_session = None;
|
||||||
let clear_client_terminal_attributes = "\u{1b}[?1l\u{1b}=\u{1b}[r\u{1b}[?1000l\u{1b}[?1002l\u{1b}[?1003l\u{1b}[?1005l\u{1b}[?1006l\u{1b}[?12l";
|
let clear_client_terminal_attributes = "\u{1b}[?1l\u{1b}=\u{1b}[r\u{1b}[?1000l\u{1b}[?1002l\u{1b}[?1003l\u{1b}[?1005l\u{1b}[?1006l\u{1b}[?12l";
|
||||||
let take_snapshot = "\u{1b}[?1049h";
|
let take_snapshot = "\u{1b}[?1049h";
|
||||||
let bracketed_paste = "\u{1b}[?2004h";
|
let bracketed_paste = "\u{1b}[?2004h";
|
||||||
|
|
@ -172,28 +182,51 @@ pub fn start_client(
|
||||||
keybinds: config.keybinds.clone(),
|
keybinds: config.keybinds.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let first_msg = match info {
|
let create_ipc_pipe = || -> std::path::PathBuf {
|
||||||
ClientInfo::Attach(name, config_options) => {
|
let mut sock_dir = ZELLIJ_SOCK_DIR.clone();
|
||||||
envs::set_session_name(name);
|
std::fs::create_dir_all(&sock_dir).unwrap();
|
||||||
|
set_permissions(&sock_dir, 0o700).unwrap();
|
||||||
|
sock_dir.push(envs::get_session_name().unwrap());
|
||||||
|
sock_dir
|
||||||
|
};
|
||||||
|
|
||||||
ClientToServerMsg::AttachClient(client_attributes, config_options)
|
let (first_msg, ipc_pipe) = match info {
|
||||||
|
ClientInfo::Attach(name, config_options) => {
|
||||||
|
envs::set_session_name(name.clone());
|
||||||
|
os_input.update_session_name(name);
|
||||||
|
let ipc_pipe = create_ipc_pipe();
|
||||||
|
|
||||||
|
(
|
||||||
|
ClientToServerMsg::AttachClient(
|
||||||
|
client_attributes,
|
||||||
|
config_options,
|
||||||
|
tab_position_to_focus,
|
||||||
|
pane_id_to_focus,
|
||||||
|
),
|
||||||
|
ipc_pipe,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
ClientInfo::New(name) => {
|
ClientInfo::New(name) => {
|
||||||
envs::set_session_name(name);
|
envs::set_session_name(name.clone());
|
||||||
|
os_input.update_session_name(name);
|
||||||
|
let ipc_pipe = create_ipc_pipe();
|
||||||
|
|
||||||
spawn_server(&*ZELLIJ_IPC_PIPE, opts.debug).unwrap();
|
spawn_server(&*ipc_pipe, opts.debug).unwrap();
|
||||||
|
|
||||||
ClientToServerMsg::NewClient(
|
(
|
||||||
client_attributes,
|
ClientToServerMsg::NewClient(
|
||||||
Box::new(opts),
|
client_attributes,
|
||||||
Box::new(config_options.clone()),
|
Box::new(opts),
|
||||||
Box::new(layout.unwrap()),
|
Box::new(config_options.clone()),
|
||||||
Some(config.plugins.clone()),
|
Box::new(layout.unwrap()),
|
||||||
|
Some(config.plugins.clone()),
|
||||||
|
),
|
||||||
|
ipc_pipe,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
os_input.connect_to_server(&*ZELLIJ_IPC_PIPE);
|
os_input.connect_to_server(&*ipc_pipe);
|
||||||
os_input.send_to_server(first_msg);
|
os_input.send_to_server(first_msg);
|
||||||
|
|
||||||
let mut command_is_executing = CommandIsExecuting::new();
|
let mut command_is_executing = CommandIsExecuting::new();
|
||||||
|
|
@ -336,7 +369,7 @@ pub fn start_client(
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
let exit_msg: String;
|
let mut exit_msg = String::new();
|
||||||
let mut loading = true;
|
let mut loading = true;
|
||||||
let mut pending_instructions = vec![];
|
let mut pending_instructions = vec![];
|
||||||
|
|
||||||
|
|
@ -415,28 +448,43 @@ pub fn start_client(
|
||||||
log::info!("{line}");
|
log::info!("{line}");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
ClientInstruction::SwitchSession(connect_to_session) => {
|
||||||
|
reconnect_to_session = Some(connect_to_session);
|
||||||
|
os_input.send_to_server(ClientToServerMsg::ClientExited);
|
||||||
|
break;
|
||||||
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
router_thread.join().unwrap();
|
router_thread.join().unwrap();
|
||||||
|
|
||||||
// cleanup();
|
if reconnect_to_session.is_none() {
|
||||||
let reset_style = "\u{1b}[m";
|
let reset_style = "\u{1b}[m";
|
||||||
let show_cursor = "\u{1b}[?25h";
|
let show_cursor = "\u{1b}[?25h";
|
||||||
let restore_snapshot = "\u{1b}[?1049l";
|
let restore_snapshot = "\u{1b}[?1049l";
|
||||||
let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1);
|
let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1);
|
||||||
let goodbye_message = format!(
|
let goodbye_message = format!(
|
||||||
"{}\n{}{}{}{}\n",
|
"{}\n{}{}{}{}\n",
|
||||||
goto_start_of_last_line, restore_snapshot, reset_style, show_cursor, exit_msg
|
goto_start_of_last_line, restore_snapshot, reset_style, show_cursor, exit_msg
|
||||||
);
|
);
|
||||||
|
|
||||||
os_input.disable_mouse().non_fatal();
|
os_input.disable_mouse().non_fatal();
|
||||||
info!("{}", exit_msg);
|
info!("{}", exit_msg);
|
||||||
os_input.unset_raw_mode(0).unwrap();
|
os_input.unset_raw_mode(0).unwrap();
|
||||||
let mut stdout = os_input.get_stdout_writer();
|
let mut stdout = os_input.get_stdout_writer();
|
||||||
let _ = stdout.write(goodbye_message.as_bytes()).unwrap();
|
let _ = stdout.write(goodbye_message.as_bytes()).unwrap();
|
||||||
stdout.flush().unwrap();
|
stdout.flush().unwrap();
|
||||||
|
} else {
|
||||||
|
let clear_screen = "\u{1b}[2J";
|
||||||
|
let mut stdout = os_input.get_stdout_writer();
|
||||||
|
let _ = stdout.write(clear_screen.as_bytes()).unwrap();
|
||||||
|
stdout.flush().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = send_input_instructions.send(InputInstruction::Exit);
|
||||||
|
|
||||||
|
reconnect_to_session
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,8 @@ pub struct ClientOsInputOutput {
|
||||||
orig_termios: Option<Arc<Mutex<termios::Termios>>>,
|
orig_termios: Option<Arc<Mutex<termios::Termios>>>,
|
||||||
send_instructions_to_server: Arc<Mutex<Option<IpcSenderWithContext<ClientToServerMsg>>>>,
|
send_instructions_to_server: Arc<Mutex<Option<IpcSenderWithContext<ClientToServerMsg>>>>,
|
||||||
receive_instructions_from_server: Arc<Mutex<Option<IpcReceiverWithContext<ServerToClientMsg>>>>,
|
receive_instructions_from_server: Arc<Mutex<Option<IpcReceiverWithContext<ServerToClientMsg>>>>,
|
||||||
|
reading_from_stdin: Arc<Mutex<Option<Vec<u8>>>>,
|
||||||
|
session_name: Arc<Mutex<Option<String>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `ClientOsApi` trait represents an abstract interface to the features of an operating system that
|
/// The `ClientOsApi` trait represents an abstract interface to the features of an operating system that
|
||||||
|
|
@ -94,8 +96,9 @@ pub trait ClientOsApi: Send + Sync {
|
||||||
/// Returns the writer that allows writing to standard output.
|
/// Returns the writer that allows writing to standard output.
|
||||||
fn get_stdout_writer(&self) -> Box<dyn io::Write>;
|
fn get_stdout_writer(&self) -> Box<dyn io::Write>;
|
||||||
fn get_stdin_reader(&self) -> Box<dyn io::Read>;
|
fn get_stdin_reader(&self) -> Box<dyn io::Read>;
|
||||||
|
fn update_session_name(&mut self, new_session_name: String);
|
||||||
/// Returns the raw contents of standard input.
|
/// Returns the raw contents of standard input.
|
||||||
fn read_from_stdin(&mut self) -> Vec<u8>;
|
fn read_from_stdin(&mut self) -> Result<Vec<u8>, &'static str>;
|
||||||
/// Returns a [`Box`] pointer to this [`ClientOsApi`] struct.
|
/// Returns a [`Box`] pointer to this [`ClientOsApi`] struct.
|
||||||
fn box_clone(&self) -> Box<dyn ClientOsApi>;
|
fn box_clone(&self) -> Box<dyn ClientOsApi>;
|
||||||
/// Sends a message to the server.
|
/// Sends a message to the server.
|
||||||
|
|
@ -135,14 +138,46 @@ impl ClientOsApi for ClientOsInputOutput {
|
||||||
fn box_clone(&self) -> Box<dyn ClientOsApi> {
|
fn box_clone(&self) -> Box<dyn ClientOsApi> {
|
||||||
Box::new((*self).clone())
|
Box::new((*self).clone())
|
||||||
}
|
}
|
||||||
fn read_from_stdin(&mut self) -> Vec<u8> {
|
fn update_session_name(&mut self, new_session_name: String) {
|
||||||
let stdin = std::io::stdin();
|
*self.session_name.lock().unwrap() = Some(new_session_name);
|
||||||
let mut stdin = stdin.lock();
|
}
|
||||||
let buffer = stdin.fill_buf().unwrap();
|
fn read_from_stdin(&mut self) -> Result<Vec<u8>, &'static str> {
|
||||||
let length = buffer.len();
|
let session_name_at_calltime = { self.session_name.lock().unwrap().clone() };
|
||||||
let read_bytes = Vec::from(buffer);
|
// here we wait for a lock in case another thread is holding stdin
|
||||||
stdin.consume(length);
|
// this can happen for example when switching sessions, the old thread will only be
|
||||||
read_bytes
|
// released once it sees input over STDIN
|
||||||
|
//
|
||||||
|
// when this happens, we detect in the other thread that our session is ended (by comparing
|
||||||
|
// the session name at the beginning of the call and the one after we read from STDIN), and
|
||||||
|
// so place what we read from STDIN inside a buffer (the "reading_from_stdin" on our state)
|
||||||
|
// and release the lock
|
||||||
|
//
|
||||||
|
// then, another thread will see there's something in the buffer immediately as it acquires
|
||||||
|
// the lock (without having to wait for STDIN itself) forward this buffer and proceed to
|
||||||
|
// wait for the "real" STDIN net time it is called
|
||||||
|
let mut buffered_bytes = self.reading_from_stdin.lock().unwrap();
|
||||||
|
match buffered_bytes.take() {
|
||||||
|
Some(buffered_bytes) => Ok(buffered_bytes),
|
||||||
|
None => {
|
||||||
|
let stdin = std::io::stdin();
|
||||||
|
let mut stdin = stdin.lock();
|
||||||
|
let buffer = stdin.fill_buf().unwrap();
|
||||||
|
let length = buffer.len();
|
||||||
|
let read_bytes = Vec::from(buffer);
|
||||||
|
stdin.consume(length);
|
||||||
|
|
||||||
|
let session_name_after_reading_from_stdin =
|
||||||
|
{ self.session_name.lock().unwrap().clone() };
|
||||||
|
if session_name_at_calltime.is_some()
|
||||||
|
&& session_name_at_calltime != session_name_after_reading_from_stdin
|
||||||
|
{
|
||||||
|
*buffered_bytes = Some(read_bytes);
|
||||||
|
Err("Session ended")
|
||||||
|
} else {
|
||||||
|
Ok(read_bytes)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn get_stdout_writer(&self) -> Box<dyn io::Write> {
|
fn get_stdout_writer(&self) -> Box<dyn io::Write> {
|
||||||
let stdout = ::std::io::stdout();
|
let stdout = ::std::io::stdout();
|
||||||
|
|
@ -258,19 +293,25 @@ impl Clone for Box<dyn ClientOsApi> {
|
||||||
pub fn get_client_os_input() -> Result<ClientOsInputOutput, nix::Error> {
|
pub fn get_client_os_input() -> Result<ClientOsInputOutput, nix::Error> {
|
||||||
let current_termios = termios::tcgetattr(0)?;
|
let current_termios = termios::tcgetattr(0)?;
|
||||||
let orig_termios = Some(Arc::new(Mutex::new(current_termios)));
|
let orig_termios = Some(Arc::new(Mutex::new(current_termios)));
|
||||||
|
let reading_from_stdin = Arc::new(Mutex::new(None));
|
||||||
Ok(ClientOsInputOutput {
|
Ok(ClientOsInputOutput {
|
||||||
orig_termios,
|
orig_termios,
|
||||||
send_instructions_to_server: Arc::new(Mutex::new(None)),
|
send_instructions_to_server: Arc::new(Mutex::new(None)),
|
||||||
receive_instructions_from_server: Arc::new(Mutex::new(None)),
|
receive_instructions_from_server: Arc::new(Mutex::new(None)),
|
||||||
|
reading_from_stdin,
|
||||||
|
session_name: Arc::new(Mutex::new(None)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_cli_client_os_input() -> Result<ClientOsInputOutput, nix::Error> {
|
pub fn get_cli_client_os_input() -> Result<ClientOsInputOutput, nix::Error> {
|
||||||
let orig_termios = None; // not a terminal
|
let orig_termios = None; // not a terminal
|
||||||
|
let reading_from_stdin = Arc::new(Mutex::new(None));
|
||||||
Ok(ClientOsInputOutput {
|
Ok(ClientOsInputOutput {
|
||||||
orig_termios,
|
orig_termios,
|
||||||
send_instructions_to_server: Arc::new(Mutex::new(None)),
|
send_instructions_to_server: Arc::new(Mutex::new(None)),
|
||||||
receive_instructions_from_server: Arc::new(Mutex::new(None)),
|
receive_instructions_from_server: Arc::new(Mutex::new(None)),
|
||||||
|
reading_from_stdin,
|
||||||
|
session_name: Arc::new(Mutex::new(None)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,66 +59,79 @@ pub(crate) fn stdin_loop(
|
||||||
}
|
}
|
||||||
let mut ansi_stdin_events = vec![];
|
let mut ansi_stdin_events = vec![];
|
||||||
loop {
|
loop {
|
||||||
let buf = os_input.read_from_stdin();
|
match os_input.read_from_stdin() {
|
||||||
{
|
Ok(buf) => {
|
||||||
// here we check if we need to parse specialized ANSI instructions sent over STDIN
|
{
|
||||||
// this happens either on startup (see above) or on SIGWINCH
|
// here we check if we need to parse specialized ANSI instructions sent over STDIN
|
||||||
//
|
// this happens either on startup (see above) or on SIGWINCH
|
||||||
// if we need to parse them, we do so with an internal timeout - anything else we
|
//
|
||||||
// receive on STDIN during that timeout is unceremoniously dropped
|
// if we need to parse them, we do so with an internal timeout - anything else we
|
||||||
let mut stdin_ansi_parser = stdin_ansi_parser.lock().unwrap();
|
// receive on STDIN during that timeout is unceremoniously dropped
|
||||||
if stdin_ansi_parser.should_parse() {
|
let mut stdin_ansi_parser = stdin_ansi_parser.lock().unwrap();
|
||||||
let events = stdin_ansi_parser.parse(buf);
|
if stdin_ansi_parser.should_parse() {
|
||||||
if !events.is_empty() {
|
let events = stdin_ansi_parser.parse(buf);
|
||||||
ansi_stdin_events.append(&mut events.clone());
|
if !events.is_empty() {
|
||||||
let _ = send_input_instructions
|
ansi_stdin_events.append(&mut events.clone());
|
||||||
.send(InputInstruction::AnsiStdinInstructions(events));
|
let _ = send_input_instructions
|
||||||
}
|
.send(InputInstruction::AnsiStdinInstructions(events));
|
||||||
continue;
|
}
|
||||||
}
|
continue;
|
||||||
}
|
|
||||||
if !ansi_stdin_events.is_empty() {
|
|
||||||
stdin_ansi_parser
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.write_cache(ansi_stdin_events.drain(..).collect());
|
|
||||||
}
|
|
||||||
current_buffer.append(&mut buf.to_vec());
|
|
||||||
let maybe_more = false; // read_from_stdin should (hopefully) always empty the STDIN buffer completely
|
|
||||||
let mut events = vec![];
|
|
||||||
input_parser.parse(
|
|
||||||
&buf,
|
|
||||||
|input_event: InputEvent| {
|
|
||||||
events.push(input_event);
|
|
||||||
},
|
|
||||||
maybe_more,
|
|
||||||
);
|
|
||||||
|
|
||||||
let event_count = events.len();
|
|
||||||
for (i, input_event) in events.into_iter().enumerate() {
|
|
||||||
if holding_mouse && is_mouse_press_or_hold(&input_event) && i == event_count - 1 {
|
|
||||||
let mut poller = os_input.stdin_poller();
|
|
||||||
loop {
|
|
||||||
if poller.ready() {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if !ansi_stdin_events.is_empty() {
|
||||||
|
stdin_ansi_parser
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.write_cache(ansi_stdin_events.drain(..).collect());
|
||||||
|
}
|
||||||
|
current_buffer.append(&mut buf.to_vec());
|
||||||
|
let maybe_more = false; // read_from_stdin should (hopefully) always empty the STDIN buffer completely
|
||||||
|
let mut events = vec![];
|
||||||
|
input_parser.parse(
|
||||||
|
&buf,
|
||||||
|
|input_event: InputEvent| {
|
||||||
|
events.push(input_event);
|
||||||
|
},
|
||||||
|
maybe_more,
|
||||||
|
);
|
||||||
|
|
||||||
|
let event_count = events.len();
|
||||||
|
for (i, input_event) in events.into_iter().enumerate() {
|
||||||
|
if holding_mouse && is_mouse_press_or_hold(&input_event) && i == event_count - 1
|
||||||
|
{
|
||||||
|
let mut poller = os_input.stdin_poller();
|
||||||
|
loop {
|
||||||
|
if poller.ready() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
send_input_instructions
|
||||||
|
.send(InputInstruction::KeyEvent(
|
||||||
|
input_event.clone(),
|
||||||
|
current_buffer.clone(),
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
holding_mouse = is_mouse_press_or_hold(&input_event);
|
||||||
|
|
||||||
send_input_instructions
|
send_input_instructions
|
||||||
.send(InputInstruction::KeyEvent(
|
.send(InputInstruction::KeyEvent(
|
||||||
input_event.clone(),
|
input_event,
|
||||||
current_buffer.clone(),
|
current_buffer.drain(..).collect(),
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
Err(e) => {
|
||||||
holding_mouse = is_mouse_press_or_hold(&input_event);
|
if e == "Session ended" {
|
||||||
|
log::debug!("Switched sessions, signing this thread off...");
|
||||||
send_input_instructions
|
} else {
|
||||||
.send(InputInstruction::KeyEvent(
|
log::error!("Failed to read from STDIN: {}", e);
|
||||||
input_event,
|
}
|
||||||
current_buffer.drain(..).collect(),
|
let _ = send_input_instructions.send(InputInstruction::Exit);
|
||||||
))
|
break;
|
||||||
.unwrap();
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -154,8 +154,9 @@ impl ClientOsApi for FakeClientOsApi {
|
||||||
fn get_stdin_reader(&self) -> Box<dyn io::Read> {
|
fn get_stdin_reader(&self) -> Box<dyn io::Read> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
fn read_from_stdin(&mut self) -> Vec<u8> {
|
fn update_session_name(&mut self, new_session_name: String) {}
|
||||||
self.stdin_buffer.drain(..).collect()
|
fn read_from_stdin(&mut self) -> Result<Vec<u8>, &'static str> {
|
||||||
|
Ok(self.stdin_buffer.drain(..).collect())
|
||||||
}
|
}
|
||||||
fn box_clone(&self) -> Box<dyn ClientOsApi> {
|
fn box_clone(&self) -> Box<dyn ClientOsApi> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,16 @@
|
||||||
use zellij_utils::async_std::task;
|
use zellij_utils::async_std::task;
|
||||||
|
use zellij_utils::consts::{ZELLIJ_SESSION_INFO_CACHE_DIR, ZELLIJ_SOCK_DIR};
|
||||||
|
use zellij_utils::data::SessionInfo;
|
||||||
use zellij_utils::errors::{prelude::*, BackgroundJobContext, ContextType};
|
use zellij_utils::errors::{prelude::*, BackgroundJobContext, ContextType};
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
use std::fs;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::os::unix::fs::FileTypeExt;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::sync::{
|
use std::sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
Arc,
|
Arc, Mutex,
|
||||||
};
|
};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
|
@ -15,8 +21,10 @@ use crate::thread_bus::Bus;
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum BackgroundJob {
|
pub enum BackgroundJob {
|
||||||
DisplayPaneError(Vec<PaneId>, String),
|
DisplayPaneError(Vec<PaneId>, String),
|
||||||
AnimatePluginLoading(u32), // u32 - plugin_id
|
AnimatePluginLoading(u32), // u32 - plugin_id
|
||||||
StopPluginLoadingAnimation(u32), // u32 - plugin_id
|
StopPluginLoadingAnimation(u32), // u32 - plugin_id
|
||||||
|
ReadAllSessionInfosOnMachine, // u32 - plugin_id
|
||||||
|
ReportSessionInfo(String, SessionInfo), // String - session name
|
||||||
Exit,
|
Exit,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -28,6 +36,10 @@ impl From<&BackgroundJob> for BackgroundJobContext {
|
||||||
BackgroundJob::StopPluginLoadingAnimation(..) => {
|
BackgroundJob::StopPluginLoadingAnimation(..) => {
|
||||||
BackgroundJobContext::StopPluginLoadingAnimation
|
BackgroundJobContext::StopPluginLoadingAnimation
|
||||||
},
|
},
|
||||||
|
BackgroundJob::ReadAllSessionInfosOnMachine => {
|
||||||
|
BackgroundJobContext::ReadAllSessionInfosOnMachine
|
||||||
|
},
|
||||||
|
BackgroundJob::ReportSessionInfo(..) => BackgroundJobContext::ReportSessionInfo,
|
||||||
BackgroundJob::Exit => BackgroundJobContext::Exit,
|
BackgroundJob::Exit => BackgroundJobContext::Exit,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -35,11 +47,14 @@ impl From<&BackgroundJob> for BackgroundJobContext {
|
||||||
|
|
||||||
static FLASH_DURATION_MS: u64 = 1000;
|
static FLASH_DURATION_MS: u64 = 1000;
|
||||||
static PLUGIN_ANIMATION_OFFSET_DURATION_MD: u64 = 500;
|
static PLUGIN_ANIMATION_OFFSET_DURATION_MD: u64 = 500;
|
||||||
|
static SESSION_READ_DURATION: u64 = 1000;
|
||||||
|
|
||||||
pub(crate) fn background_jobs_main(bus: Bus<BackgroundJob>) -> Result<()> {
|
pub(crate) fn background_jobs_main(bus: Bus<BackgroundJob>) -> Result<()> {
|
||||||
let err_context = || "failed to write to pty".to_string();
|
let err_context = || "failed to write to pty".to_string();
|
||||||
let mut running_jobs: HashMap<BackgroundJob, Instant> = HashMap::new();
|
let mut running_jobs: HashMap<BackgroundJob, Instant> = HashMap::new();
|
||||||
let mut loading_plugins: HashMap<u32, Arc<AtomicBool>> = HashMap::new(); // u32 - plugin_id
|
let mut loading_plugins: HashMap<u32, Arc<AtomicBool>> = HashMap::new(); // u32 - plugin_id
|
||||||
|
let current_session_name = Arc::new(Mutex::new(String::default()));
|
||||||
|
let current_session_info = Arc::new(Mutex::new(SessionInfo::default()));
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let (event, mut err_ctx) = bus.recv().with_context(err_context)?;
|
let (event, mut err_ctx) = bus.recv().with_context(err_context)?;
|
||||||
|
|
@ -93,10 +108,89 @@ pub(crate) fn background_jobs_main(bus: Bus<BackgroundJob>) -> Result<()> {
|
||||||
loading_plugin.store(false, Ordering::SeqCst);
|
loading_plugin.store(false, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
BackgroundJob::ReportSessionInfo(session_name, session_info) => {
|
||||||
|
*current_session_name.lock().unwrap() = session_name;
|
||||||
|
*current_session_info.lock().unwrap() = session_info;
|
||||||
|
},
|
||||||
|
BackgroundJob::ReadAllSessionInfosOnMachine => {
|
||||||
|
// this job should only be run once and it keeps track of other sessions (as well
|
||||||
|
// as this one's) infos (metadata mostly) and sends it to the screen which in turn
|
||||||
|
// forwards it to plugins and other places it needs to be
|
||||||
|
if running_jobs.get(&job).is_some() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
running_jobs.insert(job, Instant::now());
|
||||||
|
task::spawn({
|
||||||
|
let senders = bus.senders.clone();
|
||||||
|
let current_session_info = current_session_info.clone();
|
||||||
|
let current_session_name = current_session_name.clone();
|
||||||
|
async move {
|
||||||
|
loop {
|
||||||
|
// write state of current session
|
||||||
|
|
||||||
|
// write it to disk
|
||||||
|
let current_session_name =
|
||||||
|
current_session_name.lock().unwrap().to_string();
|
||||||
|
let cache_file_name =
|
||||||
|
session_info_cache_file_name(¤t_session_name);
|
||||||
|
let current_session_info = current_session_info.lock().unwrap().clone();
|
||||||
|
let _wrote_file =
|
||||||
|
std::fs::create_dir_all(ZELLIJ_SESSION_INFO_CACHE_DIR.as_path())
|
||||||
|
.and_then(|_| std::fs::File::create(cache_file_name))
|
||||||
|
.and_then(|mut f| {
|
||||||
|
write!(f, "{}", current_session_info.to_string())
|
||||||
|
});
|
||||||
|
// start a background job (if not already running) that'll periodically read this and other
|
||||||
|
// sesion infos and report back
|
||||||
|
|
||||||
|
// read state of all sessions
|
||||||
|
let mut other_session_names = vec![];
|
||||||
|
let mut session_infos_on_machine = BTreeMap::new();
|
||||||
|
// we do this so that the session infos will be actual and we're
|
||||||
|
// reasonably sure their session is running
|
||||||
|
if let Ok(files) = fs::read_dir(&*ZELLIJ_SOCK_DIR) {
|
||||||
|
files.for_each(|file| {
|
||||||
|
if let Ok(file) = file {
|
||||||
|
if let Ok(file_name) = file.file_name().into_string() {
|
||||||
|
if file.file_type().unwrap().is_socket() {
|
||||||
|
other_session_names.push(file_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for session_name in other_session_names {
|
||||||
|
let session_cache_file_name = ZELLIJ_SESSION_INFO_CACHE_DIR
|
||||||
|
.join(format!("{}.kdl", session_name));
|
||||||
|
if let Ok(raw_session_info) =
|
||||||
|
fs::read_to_string(&session_cache_file_name)
|
||||||
|
{
|
||||||
|
if let Ok(session_info) = SessionInfo::from_string(
|
||||||
|
&raw_session_info,
|
||||||
|
¤t_session_name,
|
||||||
|
) {
|
||||||
|
session_infos_on_machine.insert(session_name, session_info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let _ = senders.send_to_screen(ScreenInstruction::UpdateSessionInfos(
|
||||||
|
session_infos_on_machine,
|
||||||
|
));
|
||||||
|
task::sleep(std::time::Duration::from_millis(SESSION_READ_DURATION))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
BackgroundJob::Exit => {
|
BackgroundJob::Exit => {
|
||||||
for loading_plugin in loading_plugins.values() {
|
for loading_plugin in loading_plugins.values() {
|
||||||
loading_plugin.store(false, Ordering::SeqCst);
|
loading_plugin.store(false, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let cache_file_name =
|
||||||
|
session_info_cache_file_name(¤t_session_name.lock().unwrap().to_owned());
|
||||||
|
let _ = std::fs::remove_file(cache_file_name);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -122,3 +216,7 @@ fn job_already_running(
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn session_info_cache_file_name(session_name: &str) -> PathBuf {
|
||||||
|
ZELLIJ_SESSION_INFO_CACHE_DIR.join(format!("{}.kdl", &session_name))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ use zellij_utils::{
|
||||||
channels::{self, ChannelWithContext, SenderWithContext},
|
channels::{self, ChannelWithContext, SenderWithContext},
|
||||||
cli::CliArgs,
|
cli::CliArgs,
|
||||||
consts::{DEFAULT_SCROLL_BUFFER_SIZE, SCROLL_BUFFER_SIZE},
|
consts::{DEFAULT_SCROLL_BUFFER_SIZE, SCROLL_BUFFER_SIZE},
|
||||||
data::{Event, PluginCapabilities},
|
data::{ConnectToSession, Event, PluginCapabilities},
|
||||||
errors::{prelude::*, ContextType, ErrorInstruction, FatalError, ServerContext},
|
errors::{prelude::*, ContextType, ErrorInstruction, FatalError, ServerContext},
|
||||||
input::{
|
input::{
|
||||||
command::{RunCommand, TerminalAction},
|
command::{RunCommand, TerminalAction},
|
||||||
|
|
@ -74,10 +74,17 @@ pub enum ServerInstruction {
|
||||||
Error(String),
|
Error(String),
|
||||||
KillSession,
|
KillSession,
|
||||||
DetachSession(Vec<ClientId>),
|
DetachSession(Vec<ClientId>),
|
||||||
AttachClient(ClientAttributes, Options, ClientId),
|
AttachClient(
|
||||||
|
ClientAttributes,
|
||||||
|
Options,
|
||||||
|
Option<usize>, // tab position to focus
|
||||||
|
Option<(u32, bool)>, // (pane_id, is_plugin) => pane_id to focus
|
||||||
|
ClientId,
|
||||||
|
),
|
||||||
ConnStatus(ClientId),
|
ConnStatus(ClientId),
|
||||||
ActiveClients(ClientId),
|
ActiveClients(ClientId),
|
||||||
Log(Vec<String>, ClientId),
|
Log(Vec<String>, ClientId),
|
||||||
|
SwitchSession(ConnectToSession, ClientId),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&ServerInstruction> for ServerContext {
|
impl From<&ServerInstruction> for ServerContext {
|
||||||
|
|
@ -95,6 +102,7 @@ impl From<&ServerInstruction> for ServerContext {
|
||||||
ServerInstruction::ConnStatus(..) => ServerContext::ConnStatus,
|
ServerInstruction::ConnStatus(..) => ServerContext::ConnStatus,
|
||||||
ServerInstruction::ActiveClients(_) => ServerContext::ActiveClients,
|
ServerInstruction::ActiveClients(_) => ServerContext::ActiveClients,
|
||||||
ServerInstruction::Log(..) => ServerContext::Log,
|
ServerInstruction::Log(..) => ServerContext::Log,
|
||||||
|
ServerInstruction::SwitchSession(..) => ServerContext::SwitchSession,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -415,7 +423,13 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
|
||||||
.send_to_plugin(PluginInstruction::AddClient(client_id))
|
.send_to_plugin(PluginInstruction::AddClient(client_id))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
},
|
},
|
||||||
ServerInstruction::AttachClient(attrs, options, client_id) => {
|
ServerInstruction::AttachClient(
|
||||||
|
attrs,
|
||||||
|
options,
|
||||||
|
tab_position_to_focus,
|
||||||
|
pane_id_to_focus,
|
||||||
|
client_id,
|
||||||
|
) => {
|
||||||
let rlock = session_data.read().unwrap();
|
let rlock = session_data.read().unwrap();
|
||||||
let session_data = rlock.as_ref().unwrap();
|
let session_data = rlock.as_ref().unwrap();
|
||||||
session_state
|
session_state
|
||||||
|
|
@ -433,7 +447,11 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
session_data
|
session_data
|
||||||
.senders
|
.senders
|
||||||
.send_to_screen(ScreenInstruction::AddClient(client_id))
|
.send_to_screen(ScreenInstruction::AddClient(
|
||||||
|
client_id,
|
||||||
|
tab_position_to_focus,
|
||||||
|
pane_id_to_focus,
|
||||||
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
session_data
|
session_data
|
||||||
.senders
|
.senders
|
||||||
|
|
@ -635,6 +653,41 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
|
||||||
session_state
|
session_state
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
ServerInstruction::SwitchSession(connect_to_session, client_id) => {
|
||||||
|
if let Some(min_size) = session_state.read().unwrap().min_client_terminal_size() {
|
||||||
|
session_data
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.senders
|
||||||
|
.send_to_screen(ScreenInstruction::TerminalResize(min_size))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
session_data
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.senders
|
||||||
|
.send_to_screen(ScreenInstruction::RemoveClient(client_id))
|
||||||
|
.unwrap();
|
||||||
|
session_data
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.senders
|
||||||
|
.send_to_plugin(PluginInstruction::RemoveClient(client_id))
|
||||||
|
.unwrap();
|
||||||
|
send_to_client!(
|
||||||
|
client_id,
|
||||||
|
os_input,
|
||||||
|
ServerToClientMsg::SwitchSession(connect_to_session),
|
||||||
|
session_state
|
||||||
|
);
|
||||||
|
remove_client!(client_id, os_input, session_state);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -664,13 +717,11 @@ fn init_session(
|
||||||
plugins,
|
plugins,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
SCROLL_BUFFER_SIZE
|
let _ = SCROLL_BUFFER_SIZE.set(
|
||||||
.set(
|
config_options
|
||||||
config_options
|
.scroll_buffer_size
|
||||||
.scroll_buffer_size
|
.unwrap_or(DEFAULT_SCROLL_BUFFER_SIZE),
|
||||||
.unwrap_or(DEFAULT_SCROLL_BUFFER_SIZE),
|
);
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let (to_screen, screen_receiver): ChannelWithContext<ScreenInstruction> = channels::unbounded();
|
let (to_screen, screen_receiver): ChannelWithContext<ScreenInstruction> = channels::unbounded();
|
||||||
let to_screen = SenderWithContext::new(to_screen);
|
let to_screen = SenderWithContext::new(to_screen);
|
||||||
|
|
|
||||||
|
|
@ -1351,7 +1351,7 @@ impl Grid {
|
||||||
self.viewport.get(y).unwrap().absolute_character_index(x)
|
self.viewport.get(y).unwrap().absolute_character_index(x)
|
||||||
}
|
}
|
||||||
pub fn move_cursor_forward_until_edge(&mut self, count: usize) {
|
pub fn move_cursor_forward_until_edge(&mut self, count: usize) {
|
||||||
let count_to_move = std::cmp::min(count, self.width - self.cursor.x);
|
let count_to_move = std::cmp::min(count, self.width.saturating_sub(self.cursor.x));
|
||||||
self.cursor.x += count_to_move;
|
self.cursor.x += count_to_move;
|
||||||
}
|
}
|
||||||
pub fn replace_characters_in_line_after_cursor(&mut self, replace_with: TerminalCharacter) {
|
pub fn replace_characters_in_line_after_cursor(&mut self, replace_with: TerminalCharacter) {
|
||||||
|
|
|
||||||
|
|
@ -523,6 +523,11 @@ impl TiledPanes {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn focus_pane(&mut self, pane_id: PaneId, client_id: ClientId) {
|
pub fn focus_pane(&mut self, pane_id: PaneId, client_id: ClientId) {
|
||||||
|
if self.panes_to_hide.contains(&pane_id) {
|
||||||
|
// this means there is a fullscreen pane that is not the current pane, let's unset it
|
||||||
|
// before changing focus
|
||||||
|
self.unset_fullscreen();
|
||||||
|
}
|
||||||
if self
|
if self
|
||||||
.panes
|
.panes
|
||||||
.get(&pane_id)
|
.get(&pane_id)
|
||||||
|
|
@ -533,7 +538,6 @@ impl TiledPanes {
|
||||||
.expand_pane(&pane_id);
|
.expand_pane(&pane_id);
|
||||||
self.reapply_pane_frames();
|
self.reapply_pane_frames();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.active_panes
|
self.active_panes
|
||||||
.insert(client_id, pane_id, &mut self.panes);
|
.insert(client_id, pane_id, &mut self.panes);
|
||||||
if self.session_is_mirrored {
|
if self.session_is_mirrored {
|
||||||
|
|
|
||||||
|
|
@ -539,6 +539,7 @@ pub fn load_new_plugin_from_hd() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -609,6 +610,7 @@ pub fn plugin_workers() {
|
||||||
// we send a SystemClipboardFailure to trigger the custom handler in the fixture plugin that
|
// we send a SystemClipboardFailure to trigger the custom handler in the fixture plugin that
|
||||||
// will send a message to the worker and in turn back to the plugin to be rendered, so we know
|
// will send a message to the worker and in turn back to the plugin to be rendered, so we know
|
||||||
// that this cycle is working
|
// that this cycle is working
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -682,6 +684,7 @@ pub fn plugin_workers_persist_state() {
|
||||||
// we do this a second time so that the worker will log the first message on its own state and
|
// we do this a second time so that the worker will log the first message on its own state and
|
||||||
// then send us the "received 2 messages" indication we check for below, letting us know it
|
// then send us the "received 2 messages" indication we check for below, letting us know it
|
||||||
// managed to persist its own state and act upon it
|
// managed to persist its own state and act upon it
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -820,6 +823,7 @@ pub fn switch_to_mode_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -887,6 +891,7 @@ pub fn switch_to_mode_plugin_command_permission_denied() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -954,6 +959,7 @@ pub fn new_tabs_with_layout_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -1035,6 +1041,7 @@ pub fn new_tab_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -1102,6 +1109,7 @@ pub fn go_to_next_tab_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -1168,6 +1176,7 @@ pub fn go_to_previous_tab_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -1234,6 +1243,7 @@ pub fn resize_focused_pane_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -1300,6 +1310,7 @@ pub fn resize_focused_pane_with_direction_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -1366,6 +1377,7 @@ pub fn focus_next_pane_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -1432,6 +1444,7 @@ pub fn focus_previous_pane_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -1498,6 +1511,7 @@ pub fn move_focus_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -1564,6 +1578,7 @@ pub fn move_focus_or_tab_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -1630,6 +1645,7 @@ pub fn edit_scrollback_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -1696,6 +1712,7 @@ pub fn write_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -1762,6 +1779,7 @@ pub fn write_chars_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -1828,6 +1846,7 @@ pub fn toggle_tab_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -1894,6 +1913,7 @@ pub fn move_pane_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -1960,6 +1980,7 @@ pub fn move_pane_with_direction_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -2027,7 +2048,7 @@ pub fn clear_screen_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -2095,6 +2116,7 @@ pub fn scroll_up_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -2161,6 +2183,7 @@ pub fn scroll_down_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -2227,6 +2250,7 @@ pub fn scroll_to_top_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -2293,6 +2317,7 @@ pub fn scroll_to_bottom_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -2359,6 +2384,7 @@ pub fn page_scroll_up_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -2425,6 +2451,7 @@ pub fn page_scroll_down_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -2491,6 +2518,7 @@ pub fn toggle_focus_fullscreen_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -2557,6 +2585,7 @@ pub fn toggle_pane_frames_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -2623,6 +2652,7 @@ pub fn toggle_pane_embed_or_eject_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -2689,6 +2719,7 @@ pub fn undo_rename_pane_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -2755,6 +2786,7 @@ pub fn close_focus_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -2821,6 +2853,7 @@ pub fn toggle_active_tab_sync_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -2887,6 +2920,7 @@ pub fn close_focused_tab_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -2953,6 +2987,7 @@ pub fn undo_rename_tab_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -3019,6 +3054,7 @@ pub fn previous_swap_layout_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -3085,6 +3121,7 @@ pub fn next_swap_layout_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -3151,6 +3188,7 @@ pub fn go_to_tab_name_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -3217,6 +3255,7 @@ pub fn focus_or_create_tab_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -3283,6 +3322,7 @@ pub fn go_to_tab() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -3349,6 +3389,7 @@ pub fn start_or_reload_plugin() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -3422,6 +3463,7 @@ pub fn quit_zellij_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -3495,6 +3537,7 @@ pub fn detach_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -3568,6 +3611,7 @@ pub fn open_file_floating_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -3641,6 +3685,7 @@ pub fn open_file_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -3715,6 +3760,7 @@ pub fn open_file_with_line_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -3788,6 +3834,7 @@ pub fn open_file_with_line_floating_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -3861,6 +3908,7 @@ pub fn open_terminal_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -3934,6 +3982,7 @@ pub fn open_terminal_floating_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -4007,6 +4056,7 @@ pub fn open_command_pane_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -4080,6 +4130,7 @@ pub fn open_command_pane_floating_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -4146,6 +4197,7 @@ pub fn switch_to_tab_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -4207,6 +4259,7 @@ pub fn hide_self_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -4268,6 +4321,7 @@ pub fn show_self_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -4334,6 +4388,7 @@ pub fn close_terminal_pane_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -4400,6 +4455,7 @@ pub fn close_plugin_pane_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -4466,6 +4522,7 @@ pub fn focus_terminal_pane_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -4532,6 +4589,7 @@ pub fn focus_plugin_pane_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -4598,6 +4656,7 @@ pub fn rename_terminal_pane_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -4664,6 +4723,7 @@ pub fn rename_plugin_pane_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -4730,6 +4790,7 @@ pub fn rename_tab_plugin_command() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -4805,6 +4866,7 @@ pub fn send_configuration_to_plugins() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -4868,6 +4930,7 @@ pub fn request_plugin_permissions() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -4955,6 +5018,7 @@ pub fn granted_permission_request_result() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
@ -5040,6 +5104,7 @@ pub fn denied_permission_request_result() {
|
||||||
client_id,
|
client_id,
|
||||||
size,
|
size,
|
||||||
));
|
));
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
Some(client_id),
|
Some(client_id),
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
---
|
---
|
||||||
source: zellij-server/src/plugins/./unit/plugin_tests.rs
|
source: zellij-server/src/plugins/./unit/plugin_tests.rs
|
||||||
assertion_line: 2442
|
assertion_line: 3306
|
||||||
expression: "format!(\"{:#?}\", new_tab_event)"
|
expression: "format!(\"{:#?}\", new_tab_event)"
|
||||||
---
|
---
|
||||||
Some(
|
Some(
|
||||||
GoToTab(
|
GoToTab(
|
||||||
2,
|
3,
|
||||||
Some(
|
Some(
|
||||||
1,
|
1,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -782,6 +782,7 @@ fn check_event_permission(
|
||||||
Event::ModeUpdate(..)
|
Event::ModeUpdate(..)
|
||||||
| Event::TabUpdate(..)
|
| Event::TabUpdate(..)
|
||||||
| Event::PaneUpdate(..)
|
| Event::PaneUpdate(..)
|
||||||
|
| Event::SessionUpdate(..)
|
||||||
| Event::CopyToClipboard(..)
|
| Event::CopyToClipboard(..)
|
||||||
| Event::SystemClipboardFailure
|
| Event::SystemClipboardFailure
|
||||||
| Event::InputReceived => PermissionType::ReadApplicationState,
|
| Event::InputReceived => PermissionType::ReadApplicationState,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use super::PluginInstruction;
|
||||||
use crate::plugins::plugin_map::{PluginEnv, Subscriptions};
|
use crate::plugins::plugin_map::{PluginEnv, Subscriptions};
|
||||||
use crate::plugins::wasm_bridge::handle_plugin_crash;
|
use crate::plugins::wasm_bridge::handle_plugin_crash;
|
||||||
use crate::route::route_action;
|
use crate::route::route_action;
|
||||||
|
use crate::ServerInstruction;
|
||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::{
|
use std::{
|
||||||
|
|
@ -15,7 +16,9 @@ use std::{
|
||||||
};
|
};
|
||||||
use wasmer::{imports, Function, ImportObject, Store, WasmerEnv};
|
use wasmer::{imports, Function, ImportObject, Store, WasmerEnv};
|
||||||
use wasmer_wasi::WasiEnv;
|
use wasmer_wasi::WasiEnv;
|
||||||
use zellij_utils::data::{CommandType, PermissionStatus, PermissionType, PluginPermission};
|
use zellij_utils::data::{
|
||||||
|
CommandType, ConnectToSession, PermissionStatus, PermissionType, PluginPermission,
|
||||||
|
};
|
||||||
use zellij_utils::input::permission::PermissionCache;
|
use zellij_utils::input::permission::PermissionCache;
|
||||||
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
@ -205,6 +208,12 @@ fn host_run_plugin_command(env: &ForeignFunctionEnv) {
|
||||||
PluginCommand::RequestPluginPermissions(permissions) => {
|
PluginCommand::RequestPluginPermissions(permissions) => {
|
||||||
request_permission(env, permissions)?
|
request_permission(env, permissions)?
|
||||||
},
|
},
|
||||||
|
PluginCommand::SwitchSession(connect_to_session) => switch_session(
|
||||||
|
env,
|
||||||
|
connect_to_session.name,
|
||||||
|
connect_to_session.tab_position,
|
||||||
|
connect_to_session.pane_id,
|
||||||
|
)?,
|
||||||
},
|
},
|
||||||
(PermissionStatus::Denied, permission) => {
|
(PermissionStatus::Denied, permission) => {
|
||||||
log::error!(
|
log::error!(
|
||||||
|
|
@ -679,6 +688,31 @@ fn detach(env: &ForeignFunctionEnv) {
|
||||||
apply_action!(action, error_msg, env);
|
apply_action!(action, error_msg, env);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn switch_session(
|
||||||
|
env: &ForeignFunctionEnv,
|
||||||
|
session_name: Option<String>,
|
||||||
|
tab_position: Option<usize>,
|
||||||
|
pane_id: Option<(u32, bool)>,
|
||||||
|
) -> Result<()> {
|
||||||
|
// pane_id is (id, is_plugin)
|
||||||
|
let err_context = || format!("Failed to switch session");
|
||||||
|
let client_id = env.plugin_env.client_id;
|
||||||
|
let tab_position = tab_position.map(|p| p + 1); // ¯\_()_/¯
|
||||||
|
let connect_to_session = ConnectToSession {
|
||||||
|
name: session_name,
|
||||||
|
tab_position,
|
||||||
|
pane_id,
|
||||||
|
};
|
||||||
|
env.plugin_env
|
||||||
|
.senders
|
||||||
|
.send_to_server(ServerInstruction::SwitchSession(
|
||||||
|
connect_to_session,
|
||||||
|
client_id,
|
||||||
|
))
|
||||||
|
.with_context(err_context)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn edit_scrollback(env: &ForeignFunctionEnv) {
|
fn edit_scrollback(env: &ForeignFunctionEnv) {
|
||||||
let action = Action::EditScrollback;
|
let action = Action::EditScrollback;
|
||||||
let error_msg = || format!("Failed to edit scrollback");
|
let error_msg = || format!("Failed to edit scrollback");
|
||||||
|
|
@ -898,7 +932,7 @@ fn go_to_tab(env: &ForeignFunctionEnv, tab_index: u32) {
|
||||||
env.plugin_env.name()
|
env.plugin_env.name()
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let action = Action::GoToTab(tab_index);
|
let action = Action::GoToTab(tab_index + 1);
|
||||||
apply_action!(action, error_msg, env);
|
apply_action!(action, error_msg, env);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1113,7 +1147,6 @@ fn check_command_permission(
|
||||||
_ => return (PermissionStatus::Granted, None),
|
_ => return (PermissionStatus::Granted, None),
|
||||||
};
|
};
|
||||||
|
|
||||||
log::info!("plugin permissions: {:?}", plugin_env.permissions);
|
|
||||||
if let Some(permissions) = plugin_env.permissions.lock().unwrap().as_ref() {
|
if let Some(permissions) = plugin_env.permissions.lock().unwrap().as_ref() {
|
||||||
if permissions.contains(&permission) {
|
if permissions.contains(&permission) {
|
||||||
return (PermissionStatus::Granted, None);
|
return (PermissionStatus::Granted, None);
|
||||||
|
|
|
||||||
|
|
@ -860,9 +860,19 @@ pub(crate) fn route_thread_main(
|
||||||
.send(new_client_instruction)
|
.send(new_client_instruction)
|
||||||
.with_context(err_context)?;
|
.with_context(err_context)?;
|
||||||
},
|
},
|
||||||
ClientToServerMsg::AttachClient(client_attributes, opts) => {
|
ClientToServerMsg::AttachClient(
|
||||||
let attach_client_instruction =
|
client_attributes,
|
||||||
ServerInstruction::AttachClient(client_attributes, opts, client_id);
|
opts,
|
||||||
|
tab_position_to_focus,
|
||||||
|
pane_id_to_focus,
|
||||||
|
) => {
|
||||||
|
let attach_client_instruction = ServerInstruction::AttachClient(
|
||||||
|
client_attributes,
|
||||||
|
opts,
|
||||||
|
tab_position_to_focus,
|
||||||
|
pane_id_to_focus,
|
||||||
|
client_id,
|
||||||
|
);
|
||||||
to_server
|
to_server
|
||||||
.send(attach_client_instruction)
|
.send(attach_client_instruction)
|
||||||
.with_context(err_context)?;
|
.with_context(err_context)?;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@ use std::path::PathBuf;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
use zellij_utils::data::{Direction, PaneManifest, PluginPermission, Resize, ResizeStrategy};
|
use zellij_utils::data::{
|
||||||
|
Direction, PaneManifest, PluginPermission, Resize, ResizeStrategy, SessionInfo,
|
||||||
|
};
|
||||||
use zellij_utils::errors::prelude::*;
|
use zellij_utils::errors::prelude::*;
|
||||||
use zellij_utils::input::command::RunCommand;
|
use zellij_utils::input::command::RunCommand;
|
||||||
use zellij_utils::input::options::Clipboard;
|
use zellij_utils::input::options::Clipboard;
|
||||||
|
|
@ -242,7 +244,11 @@ pub enum ScreenInstruction {
|
||||||
MouseHoldRight(Position, ClientId),
|
MouseHoldRight(Position, ClientId),
|
||||||
MouseHoldMiddle(Position, ClientId),
|
MouseHoldMiddle(Position, ClientId),
|
||||||
Copy(ClientId),
|
Copy(ClientId),
|
||||||
AddClient(ClientId),
|
AddClient(
|
||||||
|
ClientId,
|
||||||
|
Option<usize>, // tab position to focus
|
||||||
|
Option<(u32, bool)>, // (pane_id, is_plugin) => pane_id to focus
|
||||||
|
),
|
||||||
RemoveClient(ClientId),
|
RemoveClient(ClientId),
|
||||||
AddOverlay(Overlay, ClientId),
|
AddOverlay(Overlay, ClientId),
|
||||||
RemoveOverlay(ClientId),
|
RemoveOverlay(ClientId),
|
||||||
|
|
@ -287,6 +293,7 @@ pub enum ScreenInstruction {
|
||||||
BreakPane(Box<Layout>, Option<TerminalAction>, ClientId),
|
BreakPane(Box<Layout>, Option<TerminalAction>, ClientId),
|
||||||
BreakPaneRight(ClientId),
|
BreakPaneRight(ClientId),
|
||||||
BreakPaneLeft(ClientId),
|
BreakPaneLeft(ClientId),
|
||||||
|
UpdateSessionInfos(BTreeMap<String, SessionInfo>), // String is the session name
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&ScreenInstruction> for ScreenContext {
|
impl From<&ScreenInstruction> for ScreenContext {
|
||||||
|
|
@ -460,6 +467,7 @@ impl From<&ScreenInstruction> for ScreenContext {
|
||||||
ScreenInstruction::BreakPane(..) => ScreenContext::BreakPane,
|
ScreenInstruction::BreakPane(..) => ScreenContext::BreakPane,
|
||||||
ScreenInstruction::BreakPaneRight(..) => ScreenContext::BreakPaneRight,
|
ScreenInstruction::BreakPaneRight(..) => ScreenContext::BreakPaneRight,
|
||||||
ScreenInstruction::BreakPaneLeft(..) => ScreenContext::BreakPaneLeft,
|
ScreenInstruction::BreakPaneLeft(..) => ScreenContext::BreakPaneLeft,
|
||||||
|
ScreenInstruction::UpdateSessionInfos(..) => ScreenContext::UpdateSessionInfos,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -524,6 +532,9 @@ pub(crate) struct Screen {
|
||||||
session_is_mirrored: bool,
|
session_is_mirrored: bool,
|
||||||
copy_options: CopyOptions,
|
copy_options: CopyOptions,
|
||||||
debug: bool,
|
debug: bool,
|
||||||
|
session_name: String,
|
||||||
|
session_infos_on_machine: BTreeMap<String, SessionInfo>, // String is the session name, can
|
||||||
|
// also be this session
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Screen {
|
impl Screen {
|
||||||
|
|
@ -539,6 +550,10 @@ impl Screen {
|
||||||
copy_options: CopyOptions,
|
copy_options: CopyOptions,
|
||||||
debug: bool,
|
debug: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let session_name = mode_info.session_name.clone().unwrap_or_default();
|
||||||
|
let session_info = SessionInfo::new(session_name.clone());
|
||||||
|
let mut session_infos_on_machine = BTreeMap::new();
|
||||||
|
session_infos_on_machine.insert(session_name.clone(), session_info);
|
||||||
Screen {
|
Screen {
|
||||||
bus,
|
bus,
|
||||||
max_panes,
|
max_panes,
|
||||||
|
|
@ -561,6 +576,8 @@ impl Screen {
|
||||||
session_is_mirrored,
|
session_is_mirrored,
|
||||||
copy_options,
|
copy_options,
|
||||||
debug,
|
debug,
|
||||||
|
session_name,
|
||||||
|
session_infos_on_machine,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -745,8 +762,8 @@ impl Screen {
|
||||||
.non_fatal();
|
.non_fatal();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.report_tab_state().with_context(err_context)?;
|
self.log_and_report_session_state()
|
||||||
self.report_pane_state().with_context(err_context)?;
|
.with_context(err_context)?;
|
||||||
return self.render().with_context(err_context);
|
return self.render().with_context(err_context);
|
||||||
},
|
},
|
||||||
Err(err) => Err::<(), _>(err).with_context(err_context).non_fatal(),
|
Err(err) => Err::<(), _>(err).with_context(err_context).non_fatal(),
|
||||||
|
|
@ -879,8 +896,8 @@ impl Screen {
|
||||||
t.position -= 1;
|
t.position -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.report_tab_state().with_context(err_context)?;
|
self.log_and_report_session_state()
|
||||||
self.report_pane_state().with_context(err_context)?;
|
.with_context(err_context)?;
|
||||||
self.render().with_context(err_context)
|
self.render().with_context(err_context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -917,7 +934,8 @@ impl Screen {
|
||||||
.with_context(err_context)?;
|
.with_context(err_context)?;
|
||||||
tab.set_force_render();
|
tab.set_force_render();
|
||||||
}
|
}
|
||||||
self.report_pane_state().with_context(err_context)?;
|
self.log_and_report_session_state()
|
||||||
|
.with_context(err_context)?;
|
||||||
self.render().with_context(err_context)
|
self.render().with_context(err_context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1183,10 +1201,9 @@ impl Screen {
|
||||||
self.add_client(client_id).with_context(err_context)?;
|
self.add_client(client_id).with_context(err_context)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.report_tab_state()
|
self.log_and_report_session_state()
|
||||||
.and_then(|_| self.render())
|
.and_then(|_| self.render())
|
||||||
.with_context(err_context)?;
|
.with_context(err_context)
|
||||||
self.report_pane_state().with_context(err_context)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_client(&mut self, client_id: ClientId) -> Result<()> {
|
pub fn add_client(&mut self, client_id: ClientId) -> Result<()> {
|
||||||
|
|
@ -1237,13 +1254,38 @@ impl Screen {
|
||||||
self.tab_history.remove(&client_id);
|
self.tab_history.remove(&client_id);
|
||||||
}
|
}
|
||||||
self.connected_clients.borrow_mut().remove(&client_id);
|
self.connected_clients.borrow_mut().remove(&client_id);
|
||||||
self.report_tab_state().with_context(err_context)
|
self.log_and_report_session_state()
|
||||||
|
.with_context(err_context)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn report_tab_state(&self) -> Result<()> {
|
pub fn generate_and_report_tab_state(&mut self) -> Result<Vec<TabInfo>> {
|
||||||
let mut plugin_updates = vec![];
|
let mut plugin_updates = vec![];
|
||||||
|
let mut tab_infos_for_screen_state = BTreeMap::new();
|
||||||
|
for tab in self.tabs.values() {
|
||||||
|
let all_focused_clients: Vec<ClientId> = self
|
||||||
|
.active_tab_indices
|
||||||
|
.iter()
|
||||||
|
.filter(|(_c_id, tab_position)| **tab_position == tab.index)
|
||||||
|
.map(|(c_id, _)| c_id)
|
||||||
|
.copied()
|
||||||
|
.collect();
|
||||||
|
let (active_swap_layout_name, is_swap_layout_dirty) = tab.swap_layout_info();
|
||||||
|
let tab_info_for_screen = TabInfo {
|
||||||
|
position: tab.position,
|
||||||
|
name: tab.name.clone(),
|
||||||
|
active: self.active_tab_indices.values().any(|i| i == &tab.index),
|
||||||
|
panes_to_hide: tab.panes_to_hide_count(),
|
||||||
|
is_fullscreen_active: tab.is_fullscreen_active(),
|
||||||
|
is_sync_panes_active: tab.is_sync_panes_active(),
|
||||||
|
are_floating_panes_visible: tab.are_floating_panes_visible(),
|
||||||
|
other_focused_clients: all_focused_clients,
|
||||||
|
active_swap_layout_name,
|
||||||
|
is_swap_layout_dirty,
|
||||||
|
};
|
||||||
|
tab_infos_for_screen_state.insert(tab.position, tab_info_for_screen);
|
||||||
|
}
|
||||||
for (client_id, active_tab_index) in self.active_tab_indices.iter() {
|
for (client_id, active_tab_index) in self.active_tab_indices.iter() {
|
||||||
let mut tab_data = vec![];
|
let mut plugin_tab_updates = vec![];
|
||||||
for tab in self.tabs.values() {
|
for tab in self.tabs.values() {
|
||||||
let other_focused_clients: Vec<ClientId> = if self.session_is_mirrored {
|
let other_focused_clients: Vec<ClientId> = if self.session_is_mirrored {
|
||||||
vec![]
|
vec![]
|
||||||
|
|
@ -1258,7 +1300,7 @@ impl Screen {
|
||||||
.collect()
|
.collect()
|
||||||
};
|
};
|
||||||
let (active_swap_layout_name, is_swap_layout_dirty) = tab.swap_layout_info();
|
let (active_swap_layout_name, is_swap_layout_dirty) = tab.swap_layout_info();
|
||||||
tab_data.push(TabInfo {
|
let tab_info_for_plugins = TabInfo {
|
||||||
position: tab.position,
|
position: tab.position,
|
||||||
name: tab.name.clone(),
|
name: tab.name.clone(),
|
||||||
active: *active_tab_index == tab.index,
|
active: *active_tab_index == tab.index,
|
||||||
|
|
@ -1269,17 +1311,18 @@ impl Screen {
|
||||||
other_focused_clients,
|
other_focused_clients,
|
||||||
active_swap_layout_name,
|
active_swap_layout_name,
|
||||||
is_swap_layout_dirty,
|
is_swap_layout_dirty,
|
||||||
});
|
};
|
||||||
|
plugin_tab_updates.push(tab_info_for_plugins);
|
||||||
}
|
}
|
||||||
plugin_updates.push((None, Some(*client_id), Event::TabUpdate(tab_data)));
|
plugin_updates.push((None, Some(*client_id), Event::TabUpdate(plugin_tab_updates)));
|
||||||
}
|
}
|
||||||
self.bus
|
self.bus
|
||||||
.senders
|
.senders
|
||||||
.send_to_plugin(PluginInstruction::Update(plugin_updates))
|
.send_to_plugin(PluginInstruction::Update(plugin_updates))
|
||||||
.context("failed to update tabs")?;
|
.context("failed to update tabs")?;
|
||||||
Ok(())
|
Ok(tab_infos_for_screen_state.values().cloned().collect())
|
||||||
}
|
}
|
||||||
fn report_pane_state(&self) -> Result<()> {
|
fn generate_and_report_pane_state(&mut self) -> Result<PaneManifest> {
|
||||||
let mut pane_manifest = PaneManifest::default();
|
let mut pane_manifest = PaneManifest::default();
|
||||||
for tab in self.tabs.values() {
|
for tab in self.tabs.values() {
|
||||||
pane_manifest.panes.insert(tab.position, tab.pane_infos());
|
pane_manifest.panes.insert(tab.position, tab.pane_infos());
|
||||||
|
|
@ -1289,9 +1332,51 @@ impl Screen {
|
||||||
.send_to_plugin(PluginInstruction::Update(vec![(
|
.send_to_plugin(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
Event::PaneUpdate(pane_manifest),
|
Event::PaneUpdate(pane_manifest.clone()),
|
||||||
)]))
|
)]))
|
||||||
.context("failed to update tabs")?;
|
.context("failed to update tabs")?;
|
||||||
|
|
||||||
|
Ok(pane_manifest)
|
||||||
|
}
|
||||||
|
fn log_and_report_session_state(&mut self) -> Result<()> {
|
||||||
|
let err_context = || format!("Failed to log and report session state");
|
||||||
|
// generate own session info
|
||||||
|
let pane_manifest = self.generate_and_report_pane_state()?;
|
||||||
|
let tab_infos = self.generate_and_report_tab_state()?;
|
||||||
|
let session_info = SessionInfo {
|
||||||
|
name: self.session_name.clone(),
|
||||||
|
tabs: tab_infos,
|
||||||
|
panes: pane_manifest,
|
||||||
|
connected_clients: self.active_tab_indices.keys().len(),
|
||||||
|
is_current_session: true,
|
||||||
|
};
|
||||||
|
self.bus
|
||||||
|
.senders
|
||||||
|
.send_to_background_jobs(BackgroundJob::ReportSessionInfo(
|
||||||
|
self.session_name.to_owned(),
|
||||||
|
session_info,
|
||||||
|
))
|
||||||
|
.with_context(err_context)?;
|
||||||
|
self.bus
|
||||||
|
.senders
|
||||||
|
.send_to_background_jobs(BackgroundJob::ReadAllSessionInfosOnMachine)
|
||||||
|
.with_context(err_context)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn update_session_infos(
|
||||||
|
&mut self,
|
||||||
|
new_session_infos: BTreeMap<String, SessionInfo>,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.session_infos_on_machine = new_session_infos;
|
||||||
|
self.bus
|
||||||
|
.senders
|
||||||
|
.send_to_plugin(PluginInstruction::Update(vec![(
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Event::SessionUpdate(self.session_infos_on_machine.values().cloned().collect()),
|
||||||
|
)]))
|
||||||
|
.context("failed to update session info")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1327,7 +1412,8 @@ impl Screen {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
self.report_tab_state().with_context(err_context)
|
self.log_and_report_session_state()
|
||||||
|
.with_context(err_context)
|
||||||
},
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
Err::<(), _>(err).with_context(err_context).non_fatal();
|
Err::<(), _>(err).with_context(err_context).non_fatal();
|
||||||
|
|
@ -1352,7 +1438,7 @@ impl Screen {
|
||||||
Ok(active_tab) => {
|
Ok(active_tab) => {
|
||||||
if active_tab.name != active_tab.prev_name {
|
if active_tab.name != active_tab.prev_name {
|
||||||
active_tab.name = active_tab.prev_name.clone();
|
active_tab.name = active_tab.prev_name.clone();
|
||||||
self.report_tab_state()
|
self.log_and_report_session_state()
|
||||||
.context("failed to undo renaming of active tab")?;
|
.context("failed to undo renaming of active tab")?;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -1473,7 +1559,8 @@ impl Screen {
|
||||||
Err(err) => Err::<(), _>(err).with_context(err_context).non_fatal(),
|
Err(err) => Err::<(), _>(err).with_context(err_context).non_fatal(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
self.report_pane_state().with_context(err_context)?;
|
self.log_and_report_session_state()
|
||||||
|
.with_context(err_context)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn move_focus_right_or_next_tab(&mut self, client_id: ClientId) -> Result<()> {
|
pub fn move_focus_right_or_next_tab(&mut self, client_id: ClientId) -> Result<()> {
|
||||||
|
|
@ -1508,7 +1595,8 @@ impl Screen {
|
||||||
Err(err) => Err::<(), _>(err).with_context(err_context).non_fatal(),
|
Err(err) => Err::<(), _>(err).with_context(err_context).non_fatal(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
self.report_pane_state().with_context(err_context)?;
|
self.log_and_report_session_state()
|
||||||
|
.with_context(err_context)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn toggle_tab(&mut self, client_id: ClientId) -> Result<()> {
|
pub fn toggle_tab(&mut self, client_id: ClientId) -> Result<()> {
|
||||||
|
|
@ -1521,8 +1609,8 @@ impl Screen {
|
||||||
.context("failed to toggle tabs")?;
|
.context("failed to toggle tabs")?;
|
||||||
};
|
};
|
||||||
|
|
||||||
self.report_tab_state().context("failed to toggle tabs")?;
|
self.log_and_report_session_state()
|
||||||
self.report_pane_state().context("failed to toggle tabs")?;
|
.context("failed to toggle tabs")?;
|
||||||
self.render()
|
self.render()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1550,7 +1638,8 @@ impl Screen {
|
||||||
.with_context(err_context)?
|
.with_context(err_context)?
|
||||||
.focus_pane_with_id(plugin_pane_id, should_float, client_id)
|
.focus_pane_with_id(plugin_pane_id, should_float, client_id)
|
||||||
.context("failed to focus plugin pane")?;
|
.context("failed to focus plugin pane")?;
|
||||||
self.report_pane_state().with_context(err_context)?;
|
self.log_and_report_session_state()
|
||||||
|
.with_context(err_context)?;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
},
|
},
|
||||||
None => Ok(false),
|
None => Ok(false),
|
||||||
|
|
@ -1684,8 +1773,7 @@ impl Screen {
|
||||||
new_active_tab.add_tiled_pane(active_pane, active_pane_id, Some(client_id))?;
|
new_active_tab.add_tiled_pane(active_pane, active_pane_id, Some(client_id))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.report_tab_state()?;
|
self.log_and_report_session_state()?;
|
||||||
self.report_pane_state()?;
|
|
||||||
} else {
|
} else {
|
||||||
let active_pane_id = {
|
let active_pane_id = {
|
||||||
let active_tab = self.get_active_tab_mut(client_id)?;
|
let active_tab = self.get_active_tab_mut(client_id)?;
|
||||||
|
|
@ -1843,8 +1931,7 @@ pub(crate) fn screen_thread_main(
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.report_tab_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
screen.report_pane_state()?;
|
|
||||||
|
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
},
|
},
|
||||||
|
|
@ -1852,8 +1939,7 @@ pub(crate) fn screen_thread_main(
|
||||||
active_tab!(screen, client_id, |tab: &mut Tab| tab
|
active_tab!(screen, client_id, |tab: &mut Tab| tab
|
||||||
.suppress_active_pane(pid, client_id), ?);
|
.suppress_active_pane(pid, client_id), ?);
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.report_tab_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
screen.report_pane_state()?;
|
|
||||||
|
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
},
|
},
|
||||||
|
|
@ -1861,8 +1947,7 @@ pub(crate) fn screen_thread_main(
|
||||||
active_tab_and_connected_client_id!(screen, client_id, |tab: &mut Tab, client_id: ClientId| tab
|
active_tab_and_connected_client_id!(screen, client_id, |tab: &mut Tab, client_id: ClientId| tab
|
||||||
.toggle_pane_embed_or_floating(client_id), ?);
|
.toggle_pane_embed_or_floating(client_id), ?);
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.report_tab_state()?; // update tabs so that the ui indication will be send to the plugins
|
screen.log_and_report_session_state()?;
|
||||||
screen.report_pane_state()?;
|
|
||||||
|
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
},
|
},
|
||||||
|
|
@ -1870,8 +1955,7 @@ pub(crate) fn screen_thread_main(
|
||||||
active_tab_and_connected_client_id!(screen, client_id, |tab: &mut Tab, client_id: ClientId| tab
|
active_tab_and_connected_client_id!(screen, client_id, |tab: &mut Tab, client_id: ClientId| tab
|
||||||
.toggle_floating_panes(Some(client_id), default_shell), ?);
|
.toggle_floating_panes(Some(client_id), default_shell), ?);
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.report_tab_state()?; // update tabs so that the ui indication will be send to the plugins
|
screen.log_and_report_session_state()?;
|
||||||
screen.report_pane_state()?;
|
|
||||||
|
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
},
|
},
|
||||||
|
|
@ -1901,8 +1985,7 @@ pub(crate) fn screen_thread_main(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.report_tab_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
screen.report_pane_state()?;
|
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::VerticalSplit(
|
ScreenInstruction::VerticalSplit(
|
||||||
|
|
@ -1931,8 +2014,7 @@ pub(crate) fn screen_thread_main(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.report_tab_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
screen.report_pane_state()?;
|
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::WriteCharacter(bytes, client_id) => {
|
ScreenInstruction::WriteCharacter(bytes, client_id) => {
|
||||||
|
|
@ -1953,8 +2035,7 @@ pub(crate) fn screen_thread_main(
|
||||||
?
|
?
|
||||||
);
|
);
|
||||||
if state_changed {
|
if state_changed {
|
||||||
screen.report_tab_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
screen.report_pane_state()?;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ScreenInstruction::Resize(client_id, strategy) => {
|
ScreenInstruction::Resize(client_id, strategy) => {
|
||||||
|
|
@ -1966,8 +2047,7 @@ pub(crate) fn screen_thread_main(
|
||||||
);
|
);
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.report_tab_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
screen.report_pane_state()?;
|
|
||||||
},
|
},
|
||||||
ScreenInstruction::SwitchFocus(client_id) => {
|
ScreenInstruction::SwitchFocus(client_id) => {
|
||||||
active_tab_and_connected_client_id!(
|
active_tab_and_connected_client_id!(
|
||||||
|
|
@ -1977,7 +2057,7 @@ pub(crate) fn screen_thread_main(
|
||||||
);
|
);
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.report_pane_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::FocusNextPane(client_id) => {
|
ScreenInstruction::FocusNextPane(client_id) => {
|
||||||
active_tab_and_connected_client_id!(
|
active_tab_and_connected_client_id!(
|
||||||
|
|
@ -1996,7 +2076,7 @@ pub(crate) fn screen_thread_main(
|
||||||
);
|
);
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.report_pane_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::MoveFocusLeft(client_id) => {
|
ScreenInstruction::MoveFocusLeft(client_id) => {
|
||||||
active_tab_and_connected_client_id!(
|
active_tab_and_connected_client_id!(
|
||||||
|
|
@ -2007,13 +2087,13 @@ pub(crate) fn screen_thread_main(
|
||||||
);
|
);
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.report_pane_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::MoveFocusLeftOrPreviousTab(client_id) => {
|
ScreenInstruction::MoveFocusLeftOrPreviousTab(client_id) => {
|
||||||
screen.move_focus_left_or_previous_tab(client_id)?;
|
screen.move_focus_left_or_previous_tab(client_id)?;
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.report_pane_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::MoveFocusDown(client_id) => {
|
ScreenInstruction::MoveFocusDown(client_id) => {
|
||||||
active_tab_and_connected_client_id!(
|
active_tab_and_connected_client_id!(
|
||||||
|
|
@ -2024,7 +2104,7 @@ pub(crate) fn screen_thread_main(
|
||||||
);
|
);
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.report_pane_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::MoveFocusRight(client_id) => {
|
ScreenInstruction::MoveFocusRight(client_id) => {
|
||||||
active_tab_and_connected_client_id!(
|
active_tab_and_connected_client_id!(
|
||||||
|
|
@ -2035,13 +2115,13 @@ pub(crate) fn screen_thread_main(
|
||||||
);
|
);
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.report_pane_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::MoveFocusRightOrNextTab(client_id) => {
|
ScreenInstruction::MoveFocusRightOrNextTab(client_id) => {
|
||||||
screen.move_focus_right_or_next_tab(client_id)?;
|
screen.move_focus_right_or_next_tab(client_id)?;
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.report_pane_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::MoveFocusUp(client_id) => {
|
ScreenInstruction::MoveFocusUp(client_id) => {
|
||||||
active_tab_and_connected_client_id!(
|
active_tab_and_connected_client_id!(
|
||||||
|
|
@ -2052,7 +2132,7 @@ pub(crate) fn screen_thread_main(
|
||||||
);
|
);
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.report_pane_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::ClearScreen(client_id) => {
|
ScreenInstruction::ClearScreen(client_id) => {
|
||||||
active_tab_and_connected_client_id!(
|
active_tab_and_connected_client_id!(
|
||||||
|
|
@ -2088,7 +2168,7 @@ pub(crate) fn screen_thread_main(
|
||||||
?
|
?
|
||||||
);
|
);
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.report_pane_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::ScrollUp(client_id) => {
|
ScreenInstruction::ScrollUp(client_id) => {
|
||||||
active_tab_and_connected_client_id!(
|
active_tab_and_connected_client_id!(
|
||||||
|
|
@ -2105,10 +2185,9 @@ pub(crate) fn screen_thread_main(
|
||||||
client_id,
|
client_id,
|
||||||
|tab: &mut Tab, client_id: ClientId| tab.move_active_pane(client_id)
|
|tab: &mut Tab, client_id: ClientId| tab.move_active_pane(client_id)
|
||||||
);
|
);
|
||||||
screen.report_tab_state()?; // update tabs so that the ui indication will be send to the plugins
|
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.report_pane_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::MovePaneBackwards(client_id) => {
|
ScreenInstruction::MovePaneBackwards(client_id) => {
|
||||||
active_tab_and_connected_client_id!(
|
active_tab_and_connected_client_id!(
|
||||||
|
|
@ -2116,10 +2195,9 @@ pub(crate) fn screen_thread_main(
|
||||||
client_id,
|
client_id,
|
||||||
|tab: &mut Tab, client_id: ClientId| tab.move_active_pane_backwards(client_id)
|
|tab: &mut Tab, client_id: ClientId| tab.move_active_pane_backwards(client_id)
|
||||||
);
|
);
|
||||||
screen.report_tab_state()?; // update tabs so that the ui indication will be send to the plugins
|
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.report_pane_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::MovePaneDown(client_id) => {
|
ScreenInstruction::MovePaneDown(client_id) => {
|
||||||
active_tab_and_connected_client_id!(
|
active_tab_and_connected_client_id!(
|
||||||
|
|
@ -2127,10 +2205,9 @@ pub(crate) fn screen_thread_main(
|
||||||
client_id,
|
client_id,
|
||||||
|tab: &mut Tab, client_id: ClientId| tab.move_active_pane_down(client_id)
|
|tab: &mut Tab, client_id: ClientId| tab.move_active_pane_down(client_id)
|
||||||
);
|
);
|
||||||
screen.report_tab_state()?; // update tabs so that the ui indication will be send to the plugins
|
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.report_pane_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::MovePaneUp(client_id) => {
|
ScreenInstruction::MovePaneUp(client_id) => {
|
||||||
active_tab_and_connected_client_id!(
|
active_tab_and_connected_client_id!(
|
||||||
|
|
@ -2138,10 +2215,9 @@ pub(crate) fn screen_thread_main(
|
||||||
client_id,
|
client_id,
|
||||||
|tab: &mut Tab, client_id: ClientId| tab.move_active_pane_up(client_id)
|
|tab: &mut Tab, client_id: ClientId| tab.move_active_pane_up(client_id)
|
||||||
);
|
);
|
||||||
screen.report_tab_state()?; // update tabs so that the ui indication will be send to the plugins
|
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.report_pane_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::MovePaneRight(client_id) => {
|
ScreenInstruction::MovePaneRight(client_id) => {
|
||||||
active_tab_and_connected_client_id!(
|
active_tab_and_connected_client_id!(
|
||||||
|
|
@ -2149,10 +2225,9 @@ pub(crate) fn screen_thread_main(
|
||||||
client_id,
|
client_id,
|
||||||
|tab: &mut Tab, client_id: ClientId| tab.move_active_pane_right(client_id)
|
|tab: &mut Tab, client_id: ClientId| tab.move_active_pane_right(client_id)
|
||||||
);
|
);
|
||||||
screen.report_tab_state()?; // update tabs so that the ui indication will be send to the plugins
|
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.report_pane_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::MovePaneLeft(client_id) => {
|
ScreenInstruction::MovePaneLeft(client_id) => {
|
||||||
active_tab_and_connected_client_id!(
|
active_tab_and_connected_client_id!(
|
||||||
|
|
@ -2160,10 +2235,9 @@ pub(crate) fn screen_thread_main(
|
||||||
client_id,
|
client_id,
|
||||||
|tab: &mut Tab, client_id: ClientId| tab.move_active_pane_left(client_id)
|
|tab: &mut Tab, client_id: ClientId| tab.move_active_pane_left(client_id)
|
||||||
);
|
);
|
||||||
screen.report_tab_state()?; // update tabs so that the ui indication will be send to the plugins
|
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.report_pane_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::ScrollUpAt(point, client_id) => {
|
ScreenInstruction::ScrollUpAt(point, client_id) => {
|
||||||
active_tab_and_connected_client_id!(
|
active_tab_and_connected_client_id!(
|
||||||
|
|
@ -2270,10 +2344,9 @@ pub(crate) fn screen_thread_main(
|
||||||
client_id,
|
client_id,
|
||||||
|tab: &mut Tab, client_id: ClientId| tab.close_focused_pane(client_id), ?
|
|tab: &mut Tab, client_id: ClientId| tab.close_focused_pane(client_id), ?
|
||||||
);
|
);
|
||||||
screen.report_tab_state()?;
|
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.report_pane_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::SetSelectable(id, selectable, tab_index) => {
|
ScreenInstruction::SetSelectable(id, selectable, tab_index) => {
|
||||||
screen.get_indexed_tab_mut(tab_index).map_or_else(
|
screen.get_indexed_tab_mut(tab_index).map_or_else(
|
||||||
|
|
@ -2288,7 +2361,7 @@ pub(crate) fn screen_thread_main(
|
||||||
);
|
);
|
||||||
|
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.report_pane_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::ClosePane(id, client_id) => {
|
ScreenInstruction::ClosePane(id, client_id) => {
|
||||||
match client_id {
|
match client_id {
|
||||||
|
|
@ -2308,9 +2381,8 @@ pub(crate) fn screen_thread_main(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
screen.report_tab_state()?;
|
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.report_pane_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::HoldPane(id, exit_status, run_command, tab_index, client_id) => {
|
ScreenInstruction::HoldPane(id, exit_status, run_command, tab_index, client_id) => {
|
||||||
let is_first_run = false;
|
let is_first_run = false;
|
||||||
|
|
@ -2339,9 +2411,8 @@ pub(crate) fn screen_thread_main(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
screen.report_tab_state()?;
|
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.report_pane_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::UpdatePaneName(c, client_id) => {
|
ScreenInstruction::UpdatePaneName(c, client_id) => {
|
||||||
active_tab_and_connected_client_id!(
|
active_tab_and_connected_client_id!(
|
||||||
|
|
@ -2351,7 +2422,7 @@ pub(crate) fn screen_thread_main(
|
||||||
);
|
);
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.report_pane_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::UndoRenamePane(client_id) => {
|
ScreenInstruction::UndoRenamePane(client_id) => {
|
||||||
active_tab_and_connected_client_id!(
|
active_tab_and_connected_client_id!(
|
||||||
|
|
@ -2369,10 +2440,9 @@ pub(crate) fn screen_thread_main(
|
||||||
|tab: &mut Tab, client_id: ClientId| tab
|
|tab: &mut Tab, client_id: ClientId| tab
|
||||||
.toggle_active_pane_fullscreen(client_id)
|
.toggle_active_pane_fullscreen(client_id)
|
||||||
);
|
);
|
||||||
screen.report_tab_state()?;
|
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.report_pane_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::TogglePaneFrames => {
|
ScreenInstruction::TogglePaneFrames => {
|
||||||
screen.draw_pane_frames = !screen.draw_pane_frames;
|
screen.draw_pane_frames = !screen.draw_pane_frames;
|
||||||
|
|
@ -2381,7 +2451,7 @@ pub(crate) fn screen_thread_main(
|
||||||
}
|
}
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.report_pane_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::SwitchTabNext(client_id) => {
|
ScreenInstruction::SwitchTabNext(client_id) => {
|
||||||
screen.switch_tab_next(None, true, client_id)?;
|
screen.switch_tab_next(None, true, client_id)?;
|
||||||
|
|
@ -2529,7 +2599,7 @@ pub(crate) fn screen_thread_main(
|
||||||
},
|
},
|
||||||
ScreenInstruction::TerminalResize(new_size) => {
|
ScreenInstruction::TerminalResize(new_size) => {
|
||||||
screen.resize_to_screen(new_size)?;
|
screen.resize_to_screen(new_size)?;
|
||||||
screen.report_tab_state()?; // update tabs so that the ui indication will be send to the plugins
|
screen.log_and_report_session_state()?; // update tabs so that the ui indication will be send to the plugins
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::TerminalPixelDimensions(pixel_dimensions) => {
|
ScreenInstruction::TerminalPixelDimensions(pixel_dimensions) => {
|
||||||
|
|
@ -2560,31 +2630,28 @@ pub(crate) fn screen_thread_main(
|
||||||
client_id,
|
client_id,
|
||||||
|tab: &mut Tab, _client_id: ClientId| tab.toggle_sync_panes_is_active()
|
|tab: &mut Tab, _client_id: ClientId| tab.toggle_sync_panes_is_active()
|
||||||
);
|
);
|
||||||
screen.report_tab_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::LeftClick(point, client_id) => {
|
ScreenInstruction::LeftClick(point, client_id) => {
|
||||||
active_tab!(screen, client_id, |tab: &mut Tab| tab
|
active_tab!(screen, client_id, |tab: &mut Tab| tab
|
||||||
.handle_left_click(&point, client_id), ?);
|
.handle_left_click(&point, client_id), ?);
|
||||||
screen.report_tab_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
screen.report_pane_state()?;
|
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::RightClick(point, client_id) => {
|
ScreenInstruction::RightClick(point, client_id) => {
|
||||||
active_tab!(screen, client_id, |tab: &mut Tab| tab
|
active_tab!(screen, client_id, |tab: &mut Tab| tab
|
||||||
.handle_right_click(&point, client_id), ?);
|
.handle_right_click(&point, client_id), ?);
|
||||||
screen.report_tab_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
screen.report_pane_state()?;
|
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::MiddleClick(point, client_id) => {
|
ScreenInstruction::MiddleClick(point, client_id) => {
|
||||||
active_tab!(screen, client_id, |tab: &mut Tab| tab
|
active_tab!(screen, client_id, |tab: &mut Tab| tab
|
||||||
.handle_middle_click(&point, client_id), ?);
|
.handle_middle_click(&point, client_id), ?);
|
||||||
screen.report_tab_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
screen.report_pane_state()?;
|
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
},
|
},
|
||||||
|
|
@ -2632,15 +2699,26 @@ pub(crate) fn screen_thread_main(
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::AddClient(client_id) => {
|
ScreenInstruction::AddClient(client_id, tab_position_to_focus, pane_id_to_focus) => {
|
||||||
screen.add_client(client_id)?;
|
screen.add_client(client_id)?;
|
||||||
screen.report_tab_state()?;
|
let pane_id = pane_id_to_focus.map(|(pane_id, is_plugin)| {
|
||||||
screen.report_pane_state()?;
|
if is_plugin {
|
||||||
|
PaneId::Plugin(pane_id)
|
||||||
|
} else {
|
||||||
|
PaneId::Terminal(pane_id)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if let Some(pane_id) = pane_id {
|
||||||
|
screen.focus_pane_with_id(pane_id, true, client_id)?;
|
||||||
|
} else if let Some(tab_position_to_focus) = tab_position_to_focus {
|
||||||
|
screen.go_to_tab(tab_position_to_focus, client_id)?;
|
||||||
|
}
|
||||||
|
screen.log_and_report_session_state()?;
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::RemoveClient(client_id) => {
|
ScreenInstruction::RemoveClient(client_id) => {
|
||||||
screen.remove_client(client_id)?;
|
screen.remove_client(client_id)?;
|
||||||
screen.report_pane_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::AddOverlay(overlay, _client_id) => {
|
ScreenInstruction::AddOverlay(overlay, _client_id) => {
|
||||||
|
|
@ -2755,8 +2833,7 @@ pub(crate) fn screen_thread_main(
|
||||||
?
|
?
|
||||||
);
|
);
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.report_tab_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
screen.report_pane_state()?;
|
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::NextSwapLayout(client_id) => {
|
ScreenInstruction::NextSwapLayout(client_id) => {
|
||||||
|
|
@ -2767,8 +2844,7 @@ pub(crate) fn screen_thread_main(
|
||||||
?
|
?
|
||||||
);
|
);
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.report_tab_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
screen.report_pane_state()?;
|
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::QueryTabNames(client_id) => {
|
ScreenInstruction::QueryTabNames(client_id) => {
|
||||||
|
|
@ -2852,7 +2928,7 @@ pub(crate) fn screen_thread_main(
|
||||||
} else {
|
} else {
|
||||||
log::error!("Tab index not found: {:?}", tab_index);
|
log::error!("Tab index not found: {:?}", tab_index);
|
||||||
}
|
}
|
||||||
screen.report_pane_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::UpdatePluginLoadingStage(pid, loading_indication) => {
|
ScreenInstruction::UpdatePluginLoadingStage(pid, loading_indication) => {
|
||||||
|
|
@ -2890,8 +2966,7 @@ pub(crate) fn screen_thread_main(
|
||||||
for tab in all_tabs.values_mut() {
|
for tab in all_tabs.values_mut() {
|
||||||
tab.update_input_modes()?;
|
tab.update_input_modes()?;
|
||||||
}
|
}
|
||||||
screen.report_tab_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
screen.report_pane_state()?;
|
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::LaunchOrFocusPlugin(run_plugin, should_float, client_id) => {
|
ScreenInstruction::LaunchOrFocusPlugin(run_plugin, should_float, client_id) => {
|
||||||
|
|
@ -2910,7 +2985,7 @@ pub(crate) fn screen_thread_main(
|
||||||
Some((tab_index, client_id)) => {
|
Some((tab_index, client_id)) => {
|
||||||
if screen.focus_plugin_pane(&run_plugin, should_float, client_id)? {
|
if screen.focus_plugin_pane(&run_plugin, should_float, client_id)? {
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.report_pane_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
} else {
|
} else {
|
||||||
screen.bus.senders.send_to_plugin(PluginInstruction::Load(
|
screen.bus.senders.send_to_plugin(PluginInstruction::Load(
|
||||||
Some(should_float),
|
Some(should_float),
|
||||||
|
|
@ -2934,12 +3009,11 @@ pub(crate) fn screen_thread_main(
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
screen.report_pane_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::FocusPaneWithId(pane_id, should_float_if_hidden, client_id) => {
|
ScreenInstruction::FocusPaneWithId(pane_id, should_float_if_hidden, client_id) => {
|
||||||
screen.focus_pane_with_id(pane_id, should_float_if_hidden, client_id)?;
|
screen.focus_pane_with_id(pane_id, should_float_if_hidden, client_id)?;
|
||||||
screen.report_pane_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
screen.report_tab_state()?;
|
|
||||||
},
|
},
|
||||||
ScreenInstruction::RenamePane(pane_id, new_name) => {
|
ScreenInstruction::RenamePane(pane_id, new_name) => {
|
||||||
let all_tabs = screen.get_tabs_mut();
|
let all_tabs = screen.get_tabs_mut();
|
||||||
|
|
@ -2952,7 +3026,7 @@ pub(crate) fn screen_thread_main(
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
screen.report_pane_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::RenameTab(tab_index, new_name) => {
|
ScreenInstruction::RenameTab(tab_index, new_name) => {
|
||||||
match screen.tabs.get_mut(&tab_index.saturating_sub(1)) {
|
match screen.tabs.get_mut(&tab_index.saturating_sub(1)) {
|
||||||
|
|
@ -2963,7 +3037,7 @@ pub(crate) fn screen_thread_main(
|
||||||
log::error!("Failed to find tab with index: {:?}", tab_index);
|
log::error!("Failed to find tab with index: {:?}", tab_index);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
screen.report_tab_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::RequestPluginPermissions(plugin_id, plugin_permission) => {
|
ScreenInstruction::RequestPluginPermissions(plugin_id, plugin_permission) => {
|
||||||
let all_tabs = screen.get_tabs_mut();
|
let all_tabs = screen.get_tabs_mut();
|
||||||
|
|
@ -2992,6 +3066,9 @@ pub(crate) fn screen_thread_main(
|
||||||
ScreenInstruction::BreakPaneLeft(client_id) => {
|
ScreenInstruction::BreakPaneLeft(client_id) => {
|
||||||
screen.break_pane_to_new_tab(Direction::Left, client_id)?;
|
screen.break_pane_to_new_tab(Direction::Left, client_id)?;
|
||||||
},
|
},
|
||||||
|
ScreenInstruction::UpdateSessionInfos(new_session_infos) => {
|
||||||
|
screen.update_session_infos(new_session_infos)?;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -3328,6 +3328,7 @@ impl Tab {
|
||||||
// this function is to be preferred to directly invoking floating_panes.toggle_show_panes(true)
|
// this function is to be preferred to directly invoking floating_panes.toggle_show_panes(true)
|
||||||
self.floating_panes.toggle_show_panes(true);
|
self.floating_panes.toggle_show_panes(true);
|
||||||
self.tiled_panes.unfocus_all_panes();
|
self.tiled_panes.unfocus_all_panes();
|
||||||
|
self.set_force_render();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hide_floating_panes(&mut self) {
|
pub fn hide_floating_panes(&mut self) {
|
||||||
|
|
@ -3335,6 +3336,7 @@ impl Tab {
|
||||||
// floating_panes.toggle_show_panes(false)
|
// floating_panes.toggle_show_panes(false)
|
||||||
self.floating_panes.toggle_show_panes(false);
|
self.floating_panes.toggle_show_panes(false);
|
||||||
self.tiled_panes.focus_all_panes();
|
self.tiled_panes.focus_all_panes();
|
||||||
|
self.set_force_render();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_plugin(&self, run_plugin: &RunPlugin) -> Option<PaneId> {
|
pub fn find_plugin(&self, run_plugin: &RunPlugin) -> Option<PaneId> {
|
||||||
|
|
@ -3359,6 +3361,7 @@ impl Tab {
|
||||||
// TODO: should error if pane is not selectable
|
// TODO: should error if pane is not selectable
|
||||||
self.tiled_panes
|
self.tiled_panes
|
||||||
.focus_pane_if_exists(pane_id, client_id)
|
.focus_pane_if_exists(pane_id, client_id)
|
||||||
|
.map(|_| self.hide_floating_panes())
|
||||||
.or_else(|_| {
|
.or_else(|_| {
|
||||||
let focused_floating_pane =
|
let focused_floating_pane =
|
||||||
self.floating_panes.focus_pane_if_exists(pane_id, client_id);
|
self.floating_panes.focus_pane_if_exists(pane_id, client_id);
|
||||||
|
|
|
||||||
|
|
@ -563,6 +563,34 @@ where
|
||||||
unsafe { host_run_plugin_command() };
|
unsafe { host_run_plugin_command() };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Switch to a session with the given name, create one if no name is given
|
||||||
|
pub fn switch_session(name: Option<&str>) {
|
||||||
|
let plugin_command = PluginCommand::SwitchSession(ConnectToSession {
|
||||||
|
name: name.map(|n| n.to_string()),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
|
||||||
|
object_to_stdout(&protobuf_plugin_command.encode_to_vec());
|
||||||
|
unsafe { host_run_plugin_command() };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Switch to a session with the given name, focusing either the provided pane_id or the provided
|
||||||
|
/// tab position (in that order)
|
||||||
|
pub fn switch_session_with_focus(
|
||||||
|
name: &str,
|
||||||
|
tab_position: Option<usize>,
|
||||||
|
pane_id: Option<(u32, bool)>,
|
||||||
|
) {
|
||||||
|
let plugin_command = PluginCommand::SwitchSession(ConnectToSession {
|
||||||
|
name: Some(name.to_owned()),
|
||||||
|
tab_position,
|
||||||
|
pane_id,
|
||||||
|
});
|
||||||
|
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
|
// Utility Functions
|
||||||
|
|
||||||
/// Returns the `TabInfo` corresponding to the currently active tab
|
/// Returns the `TabInfo` corresponding to the currently active tab
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,12 @@ keybinds {
|
||||||
bind "Ctrl o" { SwitchToMode "Normal"; }
|
bind "Ctrl o" { SwitchToMode "Normal"; }
|
||||||
bind "Ctrl s" { SwitchToMode "Scroll"; }
|
bind "Ctrl s" { SwitchToMode "Scroll"; }
|
||||||
bind "d" { Detach; }
|
bind "d" { Detach; }
|
||||||
|
bind "w" {
|
||||||
|
LaunchOrFocusPlugin "zellij:session-manager" {
|
||||||
|
floating true
|
||||||
|
};
|
||||||
|
SwitchToMode "Normal"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
tmux {
|
tmux {
|
||||||
bind "[" { SwitchToMode "Scroll"; }
|
bind "[" { SwitchToMode "Scroll"; }
|
||||||
|
|
@ -181,6 +187,7 @@ plugins {
|
||||||
status-bar { path "status-bar"; }
|
status-bar { path "status-bar"; }
|
||||||
strider { path "strider"; }
|
strider { path "strider"; }
|
||||||
compact-bar { path "compact-bar"; }
|
compact-bar { path "compact-bar"; }
|
||||||
|
session-manager { path "session-manager"; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Choose what to do when zellij receives SIGTERM, SIGINT, SIGQUIT or SIGHUP
|
// Choose what to do when zellij receives SIGTERM, SIGINT, SIGQUIT or SIGHUP
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
BIN
zellij-utils/assets/plugins/session-manager.wasm
Executable file
BIN
zellij-utils/assets/plugins/session-manager.wasm
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -38,6 +38,8 @@ lazy_static! {
|
||||||
.join(format!("{}", Uuid::new_v4()));
|
.join(format!("{}", Uuid::new_v4()));
|
||||||
pub static ref ZELLIJ_PLUGIN_PERMISSIONS_CACHE: PathBuf =
|
pub static ref ZELLIJ_PLUGIN_PERMISSIONS_CACHE: PathBuf =
|
||||||
ZELLIJ_CACHE_DIR.join("permissions.kdl");
|
ZELLIJ_CACHE_DIR.join("permissions.kdl");
|
||||||
|
pub static ref ZELLIJ_SESSION_INFO_CACHE_DIR: PathBuf =
|
||||||
|
ZELLIJ_CACHE_DIR.join(VERSION).join("session_info");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const FEATURES: &[&str] = &[
|
pub const FEATURES: &[&str] = &[
|
||||||
|
|
@ -92,6 +94,7 @@ mod not_wasm {
|
||||||
add_plugin!(assets, "status-bar.wasm");
|
add_plugin!(assets, "status-bar.wasm");
|
||||||
add_plugin!(assets, "tab-bar.wasm");
|
add_plugin!(assets, "tab-bar.wasm");
|
||||||
add_plugin!(assets, "strider.wasm");
|
add_plugin!(assets, "strider.wasm");
|
||||||
|
add_plugin!(assets, "session-manager.wasm");
|
||||||
assets
|
assets
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -104,20 +107,13 @@ pub use unix_only::*;
|
||||||
mod unix_only {
|
mod unix_only {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::envs;
|
use crate::envs;
|
||||||
use crate::shared::set_permissions;
|
pub use crate::shared::set_permissions;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use nix::unistd::Uid;
|
use nix::unistd::Uid;
|
||||||
use std::{env::temp_dir, fs};
|
use std::env::temp_dir;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref UID: Uid = Uid::current();
|
static ref UID: Uid = Uid::current();
|
||||||
pub static ref ZELLIJ_IPC_PIPE: PathBuf = {
|
|
||||||
let mut sock_dir = ZELLIJ_SOCK_DIR.clone();
|
|
||||||
fs::create_dir_all(&sock_dir).unwrap();
|
|
||||||
set_permissions(&sock_dir, 0o700).unwrap();
|
|
||||||
sock_dir.push(envs::get_session_name().unwrap());
|
|
||||||
sock_dir
|
|
||||||
};
|
|
||||||
pub static ref ZELLIJ_TMP_DIR: PathBuf = temp_dir().join(format!("zellij-{}", *UID));
|
pub static ref ZELLIJ_TMP_DIR: PathBuf = temp_dir().join(format!("zellij-{}", *UID));
|
||||||
pub static ref ZELLIJ_TMP_LOG_DIR: PathBuf = ZELLIJ_TMP_DIR.join("zellij-log");
|
pub static ref ZELLIJ_TMP_LOG_DIR: PathBuf = ZELLIJ_TMP_DIR.join("zellij-log");
|
||||||
pub static ref ZELLIJ_TMP_LOG_FILE: PathBuf = ZELLIJ_TMP_LOG_DIR.join("zellij.log");
|
pub static ref ZELLIJ_TMP_LOG_FILE: PathBuf = ZELLIJ_TMP_LOG_DIR.join("zellij.log");
|
||||||
|
|
|
||||||
|
|
@ -495,6 +495,7 @@ pub enum Event {
|
||||||
FileSystemDelete(Vec<PathBuf>),
|
FileSystemDelete(Vec<PathBuf>),
|
||||||
/// A Result of plugin permission request
|
/// A Result of plugin permission request
|
||||||
PermissionRequestResult(PermissionStatus),
|
PermissionRequestResult(PermissionStatus),
|
||||||
|
SessionUpdate(Vec<SessionInfo>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
|
|
@ -734,6 +735,42 @@ impl ModeInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
|
pub struct SessionInfo {
|
||||||
|
pub name: String,
|
||||||
|
pub tabs: Vec<TabInfo>,
|
||||||
|
pub panes: PaneManifest,
|
||||||
|
pub connected_clients: usize,
|
||||||
|
pub is_current_session: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
|
#[allow(clippy::derive_hash_xor_eq)]
|
||||||
|
impl Hash for SessionInfo {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.name.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SessionInfo {
|
||||||
|
pub fn new(name: String) -> Self {
|
||||||
|
SessionInfo {
|
||||||
|
name,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn update_tab_info(&mut self, new_tab_info: Vec<TabInfo>) {
|
||||||
|
self.tabs = new_tab_info;
|
||||||
|
}
|
||||||
|
pub fn update_pane_info(&mut self, new_pane_info: PaneManifest) {
|
||||||
|
self.panes = new_pane_info;
|
||||||
|
}
|
||||||
|
pub fn update_connected_clients(&mut self, new_connected_clients: usize) {
|
||||||
|
self.connected_clients = new_connected_clients;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Contains all the information for a currently opened tab.
|
/// Contains all the information for a currently opened tab.
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||||
pub struct TabInfo {
|
pub struct TabInfo {
|
||||||
|
|
@ -921,6 +958,13 @@ impl CommandToRun {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ConnectToSession {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub tab_position: Option<usize>,
|
||||||
|
pub pane_id: Option<(u32, bool)>, // (id, is_plugin)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct PluginMessage {
|
pub struct PluginMessage {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
@ -1016,4 +1060,5 @@ pub enum PluginCommand {
|
||||||
RenameTab(u32, String), // tab index, new name
|
RenameTab(u32, String), // tab index, new name
|
||||||
ReportPanic(String), // stringified panic
|
ReportPanic(String), // stringified panic
|
||||||
RequestPluginPermissions(Vec<PermissionType>),
|
RequestPluginPermissions(Vec<PermissionType>),
|
||||||
|
SwitchSession(ConnectToSession),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -342,6 +342,7 @@ pub enum ScreenContext {
|
||||||
BreakPane,
|
BreakPane,
|
||||||
BreakPaneRight,
|
BreakPaneRight,
|
||||||
BreakPaneLeft,
|
BreakPaneLeft,
|
||||||
|
UpdateSessionInfos,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stack call representations corresponding to the different types of [`PtyInstruction`]s.
|
/// Stack call representations corresponding to the different types of [`PtyInstruction`]s.
|
||||||
|
|
@ -396,6 +397,7 @@ pub enum ClientContext {
|
||||||
OwnClientId,
|
OwnClientId,
|
||||||
StartedParsingStdinQuery,
|
StartedParsingStdinQuery,
|
||||||
DoneParsingStdinQuery,
|
DoneParsingStdinQuery,
|
||||||
|
SwitchSession,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stack call representations corresponding to the different types of [`ServerInstruction`]s.
|
/// Stack call representations corresponding to the different types of [`ServerInstruction`]s.
|
||||||
|
|
@ -413,6 +415,7 @@ pub enum ServerContext {
|
||||||
ConnStatus,
|
ConnStatus,
|
||||||
ActiveClients,
|
ActiveClients,
|
||||||
Log,
|
Log,
|
||||||
|
SwitchSession,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||||
|
|
@ -429,6 +432,8 @@ pub enum BackgroundJobContext {
|
||||||
DisplayPaneError,
|
DisplayPaneError,
|
||||||
AnimatePluginLoading,
|
AnimatePluginLoading,
|
||||||
StopPluginLoadingAnimation,
|
StopPluginLoadingAnimation,
|
||||||
|
ReadAllSessionInfosOnMachine,
|
||||||
|
ReportSessionInfo,
|
||||||
Exit,
|
Exit,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,12 +27,16 @@ impl PermissionCache {
|
||||||
pub fn check_permissions(
|
pub fn check_permissions(
|
||||||
&self,
|
&self,
|
||||||
plugin_name: String,
|
plugin_name: String,
|
||||||
permissions: &Vec<PermissionType>,
|
permissions_to_check: &Vec<PermissionType>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if let Some(target) = self.granted.get(&plugin_name) {
|
if let Some(target) = self.granted.get(&plugin_name) {
|
||||||
if target == permissions {
|
let mut all_granted = true;
|
||||||
return true;
|
for permission in permissions_to_check {
|
||||||
|
if !target.contains(permission) {
|
||||||
|
all_granted = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return all_granted;
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
false
|
||||||
|
|
@ -43,7 +47,10 @@ impl PermissionCache {
|
||||||
|
|
||||||
let granted = match fs::read_to_string(cache_path.clone()) {
|
let granted = match fs::read_to_string(cache_path.clone()) {
|
||||||
Ok(raw_string) => PermissionCache::from_string(raw_string).unwrap_or_default(),
|
Ok(raw_string) => PermissionCache::from_string(raw_string).unwrap_or_default(),
|
||||||
Err(_) => GrantedPermission::default(),
|
Err(e) => {
|
||||||
|
log::error!("Failed to read permission cache file: {}", e);
|
||||||
|
GrantedPermission::default()
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
PermissionCache {
|
PermissionCache {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
//! IPC stuff for starting to split things into a client and server model.
|
//! IPC stuff for starting to split things into a client and server model.
|
||||||
use crate::{
|
use crate::{
|
||||||
cli::CliArgs,
|
cli::CliArgs,
|
||||||
data::{ClientId, InputMode, Style},
|
data::{ClientId, ConnectToSession, InputMode, Style},
|
||||||
errors::{get_current_ctx, prelude::*, ErrorContext},
|
errors::{get_current_ctx, prelude::*, ErrorContext},
|
||||||
input::keybinds::Keybinds,
|
input::keybinds::Keybinds,
|
||||||
input::{actions::Action, layout::Layout, options::Options, plugins::PluginsConfig},
|
input::{actions::Action, layout::Layout, options::Options, plugins::PluginsConfig},
|
||||||
|
|
@ -65,16 +65,6 @@ impl PixelDimensions {
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub enum ClientToServerMsg {
|
pub enum ClientToServerMsg {
|
||||||
/*// List which sessions are available
|
|
||||||
ListSessions,
|
|
||||||
// Create a new session
|
|
||||||
CreateSession,
|
|
||||||
// Attach to a running session
|
|
||||||
AttachToSession(SessionId, ClientType),
|
|
||||||
// Force detach
|
|
||||||
DetachSession(SessionId),
|
|
||||||
// Disconnect from the session we're connected to
|
|
||||||
DisconnectFromSession,*/
|
|
||||||
DetachSession(Vec<ClientId>),
|
DetachSession(Vec<ClientId>),
|
||||||
TerminalPixelDimensions(PixelDimensions),
|
TerminalPixelDimensions(PixelDimensions),
|
||||||
BackgroundColor(String),
|
BackgroundColor(String),
|
||||||
|
|
@ -88,7 +78,12 @@ pub enum ClientToServerMsg {
|
||||||
Box<Layout>,
|
Box<Layout>,
|
||||||
Option<PluginsConfig>,
|
Option<PluginsConfig>,
|
||||||
),
|
),
|
||||||
AttachClient(ClientAttributes, Options),
|
AttachClient(
|
||||||
|
ClientAttributes,
|
||||||
|
Options,
|
||||||
|
Option<usize>, // tab position to focus
|
||||||
|
Option<(u32, bool)>, // (pane_id, is_plugin) => pane id to focus
|
||||||
|
),
|
||||||
Action(Action, Option<ClientId>),
|
Action(Action, Option<ClientId>),
|
||||||
ClientExited,
|
ClientExited,
|
||||||
KillSession,
|
KillSession,
|
||||||
|
|
@ -99,10 +94,6 @@ pub enum ClientToServerMsg {
|
||||||
// Types of messages sent from the server to the client
|
// Types of messages sent from the server to the client
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub enum ServerToClientMsg {
|
pub enum ServerToClientMsg {
|
||||||
/*// Info about a particular session
|
|
||||||
SessionInfo(Session),
|
|
||||||
// A list of sessions
|
|
||||||
SessionList(HashSet<Session>),*/
|
|
||||||
Render(String),
|
Render(String),
|
||||||
UnblockInputThread,
|
UnblockInputThread,
|
||||||
Exit(ExitReason),
|
Exit(ExitReason),
|
||||||
|
|
@ -110,6 +101,7 @@ pub enum ServerToClientMsg {
|
||||||
Connected,
|
Connected,
|
||||||
ActiveClients(Vec<ClientId>),
|
ActiveClients(Vec<ClientId>),
|
||||||
Log(Vec<String>),
|
Log(Vec<String>),
|
||||||
|
SwitchSession(ConnectToSession),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
mod kdl_layout_parser;
|
mod kdl_layout_parser;
|
||||||
use crate::data::{Direction, InputMode, Key, Palette, PaletteColor, PermissionType, Resize};
|
use crate::data::{
|
||||||
|
Direction, InputMode, Key, Palette, PaletteColor, PaneInfo, PaneManifest, PermissionType,
|
||||||
|
Resize, SessionInfo, TabInfo,
|
||||||
|
};
|
||||||
use crate::envs::EnvironmentVariables;
|
use crate::envs::EnvironmentVariables;
|
||||||
use crate::input::config::{Config, ConfigError, KdlError};
|
use crate::input::config::{Config, ConfigError, KdlError};
|
||||||
use crate::input::keybinds::Keybinds;
|
use crate::input::keybinds::Keybinds;
|
||||||
|
|
@ -325,7 +328,6 @@ macro_rules! actions_from_kdl {
|
||||||
pub fn kdl_arguments_that_are_strings<'a>(
|
pub fn kdl_arguments_that_are_strings<'a>(
|
||||||
arguments: impl Iterator<Item = &'a KdlEntry>,
|
arguments: impl Iterator<Item = &'a KdlEntry>,
|
||||||
) -> Result<Vec<String>, ConfigError> {
|
) -> Result<Vec<String>, ConfigError> {
|
||||||
// pub fn kdl_arguments_that_are_strings <'a>(arguments: impl Iterator<Item=&'a KdlValue>) -> Result<Vec<String>, ConfigError> {
|
|
||||||
let mut args: Vec<String> = vec![];
|
let mut args: Vec<String> = vec![];
|
||||||
for kdl_entry in arguments {
|
for kdl_entry in arguments {
|
||||||
match kdl_entry.value().as_string() {
|
match kdl_entry.value().as_string() {
|
||||||
|
|
@ -1841,6 +1843,419 @@ impl PermissionCache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SessionInfo {
|
||||||
|
pub fn from_string(raw_session_info: &str, current_session_name: &str) -> Result<Self, String> {
|
||||||
|
let kdl_document: KdlDocument = raw_session_info
|
||||||
|
.parse()
|
||||||
|
.map_err(|e| format!("Failed to parse kdl document: {}", e))?;
|
||||||
|
let name = kdl_document
|
||||||
|
.get("name")
|
||||||
|
.and_then(|n| n.entries().iter().next())
|
||||||
|
.and_then(|e| e.value().as_string())
|
||||||
|
.map(|s| s.to_owned())
|
||||||
|
.ok_or("Failed to parse session name")?;
|
||||||
|
let connected_clients = kdl_document
|
||||||
|
.get("connected_clients")
|
||||||
|
.and_then(|n| n.entries().iter().next())
|
||||||
|
.and_then(|e| e.value().as_i64())
|
||||||
|
.map(|c| c as usize)
|
||||||
|
.ok_or("Failed to parse connected_clients")?;
|
||||||
|
let tabs: Vec<TabInfo> = kdl_document
|
||||||
|
.get("tabs")
|
||||||
|
.and_then(|t| t.children())
|
||||||
|
.and_then(|c| {
|
||||||
|
let mut tab_nodes = vec![];
|
||||||
|
for tab_node in c.nodes() {
|
||||||
|
if let Some(tab) = tab_node.children() {
|
||||||
|
tab_nodes.push(TabInfo::decode_from_kdl(tab).ok()?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(tab_nodes)
|
||||||
|
})
|
||||||
|
.ok_or("Failed to parse tabs")?;
|
||||||
|
let panes: PaneManifest = kdl_document
|
||||||
|
.get("panes")
|
||||||
|
.and_then(|p| p.children())
|
||||||
|
.map(|p| PaneManifest::decode_from_kdl(p))
|
||||||
|
.ok_or("Failed to parse panes")?;
|
||||||
|
let is_current_session = name == current_session_name;
|
||||||
|
Ok(SessionInfo {
|
||||||
|
name,
|
||||||
|
tabs,
|
||||||
|
panes,
|
||||||
|
connected_clients,
|
||||||
|
is_current_session,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn to_string(&self) -> String {
|
||||||
|
let mut kdl_document = KdlDocument::new();
|
||||||
|
|
||||||
|
let mut name = KdlNode::new("name");
|
||||||
|
name.push(self.name.clone());
|
||||||
|
|
||||||
|
let mut connected_clients = KdlNode::new("connected_clients");
|
||||||
|
connected_clients.push(self.connected_clients as i64);
|
||||||
|
|
||||||
|
let mut tabs = KdlNode::new("tabs");
|
||||||
|
let mut tab_children = KdlDocument::new();
|
||||||
|
for tab_info in &self.tabs {
|
||||||
|
let mut tab = KdlNode::new("tab");
|
||||||
|
let kdl_tab_info = tab_info.encode_to_kdl();
|
||||||
|
tab.set_children(kdl_tab_info);
|
||||||
|
tab_children.nodes_mut().push(tab);
|
||||||
|
}
|
||||||
|
tabs.set_children(tab_children);
|
||||||
|
|
||||||
|
let mut panes = KdlNode::new("panes");
|
||||||
|
panes.set_children(self.panes.encode_to_kdl());
|
||||||
|
|
||||||
|
kdl_document.nodes_mut().push(name);
|
||||||
|
kdl_document.nodes_mut().push(tabs);
|
||||||
|
kdl_document.nodes_mut().push(panes);
|
||||||
|
kdl_document.nodes_mut().push(connected_clients);
|
||||||
|
kdl_document.fmt();
|
||||||
|
kdl_document.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TabInfo {
|
||||||
|
pub fn decode_from_kdl(kdl_document: &KdlDocument) -> Result<Self, String> {
|
||||||
|
macro_rules! int_node {
|
||||||
|
($name:expr, $type:ident) => {{
|
||||||
|
kdl_document
|
||||||
|
.get($name)
|
||||||
|
.and_then(|n| n.entries().iter().next())
|
||||||
|
.and_then(|e| e.value().as_i64())
|
||||||
|
.map(|e| e as $type)
|
||||||
|
.ok_or(format!("Failed to parse tab {}", $name))?
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
macro_rules! string_node {
|
||||||
|
($name:expr) => {{
|
||||||
|
kdl_document
|
||||||
|
.get($name)
|
||||||
|
.and_then(|n| n.entries().iter().next())
|
||||||
|
.and_then(|e| e.value().as_string())
|
||||||
|
.map(|s| s.to_owned())
|
||||||
|
.ok_or(format!("Failed to parse tab {}", $name))?
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
macro_rules! optional_string_node {
|
||||||
|
($name:expr) => {{
|
||||||
|
kdl_document
|
||||||
|
.get($name)
|
||||||
|
.and_then(|n| n.entries().iter().next())
|
||||||
|
.and_then(|e| e.value().as_string())
|
||||||
|
.map(|s| s.to_owned())
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
macro_rules! bool_node {
|
||||||
|
($name:expr) => {{
|
||||||
|
kdl_document
|
||||||
|
.get($name)
|
||||||
|
.and_then(|n| n.entries().iter().next())
|
||||||
|
.and_then(|e| e.value().as_bool())
|
||||||
|
.ok_or(format!("Failed to parse tab {}", $name))?
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
let position = int_node!("position", usize);
|
||||||
|
let name = string_node!("name");
|
||||||
|
let active = bool_node!("active");
|
||||||
|
let panes_to_hide = int_node!("panes_to_hide", usize);
|
||||||
|
let is_fullscreen_active = bool_node!("is_fullscreen_active");
|
||||||
|
let is_sync_panes_active = bool_node!("is_sync_panes_active");
|
||||||
|
let are_floating_panes_visible = bool_node!("are_floating_panes_visible");
|
||||||
|
let mut other_focused_clients = vec![];
|
||||||
|
if let Some(tab_other_focused_clients) = kdl_document
|
||||||
|
.get("other_focused_clients")
|
||||||
|
.map(|n| n.entries())
|
||||||
|
{
|
||||||
|
for entry in tab_other_focused_clients {
|
||||||
|
if let Some(entry_parsed) = entry.value().as_i64() {
|
||||||
|
other_focused_clients.push(entry_parsed as u16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let active_swap_layout_name = optional_string_node!("active_swap_layout_name");
|
||||||
|
let is_swap_layout_dirty = bool_node!("is_swap_layout_dirty");
|
||||||
|
Ok(TabInfo {
|
||||||
|
position,
|
||||||
|
name,
|
||||||
|
active,
|
||||||
|
panes_to_hide,
|
||||||
|
is_fullscreen_active,
|
||||||
|
is_sync_panes_active,
|
||||||
|
are_floating_panes_visible,
|
||||||
|
other_focused_clients,
|
||||||
|
active_swap_layout_name,
|
||||||
|
is_swap_layout_dirty,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn encode_to_kdl(&self) -> KdlDocument {
|
||||||
|
let mut kdl_doucment = KdlDocument::new();
|
||||||
|
|
||||||
|
let mut position = KdlNode::new("position");
|
||||||
|
position.push(self.position as i64);
|
||||||
|
kdl_doucment.nodes_mut().push(position);
|
||||||
|
|
||||||
|
let mut name = KdlNode::new("name");
|
||||||
|
name.push(self.name.clone());
|
||||||
|
kdl_doucment.nodes_mut().push(name);
|
||||||
|
|
||||||
|
let mut active = KdlNode::new("active");
|
||||||
|
active.push(self.active);
|
||||||
|
kdl_doucment.nodes_mut().push(active);
|
||||||
|
|
||||||
|
let mut panes_to_hide = KdlNode::new("panes_to_hide");
|
||||||
|
panes_to_hide.push(self.panes_to_hide as i64);
|
||||||
|
kdl_doucment.nodes_mut().push(panes_to_hide);
|
||||||
|
|
||||||
|
let mut is_fullscreen_active = KdlNode::new("is_fullscreen_active");
|
||||||
|
is_fullscreen_active.push(self.is_fullscreen_active);
|
||||||
|
kdl_doucment.nodes_mut().push(is_fullscreen_active);
|
||||||
|
|
||||||
|
let mut is_sync_panes_active = KdlNode::new("is_sync_panes_active");
|
||||||
|
is_sync_panes_active.push(self.is_sync_panes_active);
|
||||||
|
kdl_doucment.nodes_mut().push(is_sync_panes_active);
|
||||||
|
|
||||||
|
let mut are_floating_panes_visible = KdlNode::new("are_floating_panes_visible");
|
||||||
|
are_floating_panes_visible.push(self.are_floating_panes_visible);
|
||||||
|
kdl_doucment.nodes_mut().push(are_floating_panes_visible);
|
||||||
|
|
||||||
|
if !self.other_focused_clients.is_empty() {
|
||||||
|
let mut other_focused_clients = KdlNode::new("other_focused_clients");
|
||||||
|
for client_id in &self.other_focused_clients {
|
||||||
|
other_focused_clients.push(*client_id as i64);
|
||||||
|
}
|
||||||
|
kdl_doucment.nodes_mut().push(other_focused_clients);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(active_swap_layout_name) = self.active_swap_layout_name.as_ref() {
|
||||||
|
let mut active_swap_layout = KdlNode::new("active_swap_layout_name");
|
||||||
|
active_swap_layout.push(active_swap_layout_name.to_string());
|
||||||
|
kdl_doucment.nodes_mut().push(active_swap_layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut is_swap_layout_dirty = KdlNode::new("is_swap_layout_dirty");
|
||||||
|
is_swap_layout_dirty.push(self.is_swap_layout_dirty);
|
||||||
|
kdl_doucment.nodes_mut().push(is_swap_layout_dirty);
|
||||||
|
|
||||||
|
kdl_doucment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PaneManifest {
|
||||||
|
pub fn decode_from_kdl(kdl_doucment: &KdlDocument) -> Self {
|
||||||
|
let mut panes: HashMap<usize, Vec<PaneInfo>> = HashMap::new();
|
||||||
|
for node in kdl_doucment.nodes() {
|
||||||
|
if node.name().to_string() == "pane" {
|
||||||
|
if let Some(pane_document) = node.children() {
|
||||||
|
if let Ok((tab_position, pane_info)) = PaneInfo::decode_from_kdl(pane_document)
|
||||||
|
{
|
||||||
|
let panes_in_tab_position =
|
||||||
|
panes.entry(tab_position).or_insert_with(Vec::new);
|
||||||
|
panes_in_tab_position.push(pane_info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PaneManifest { panes }
|
||||||
|
}
|
||||||
|
pub fn encode_to_kdl(&self) -> KdlDocument {
|
||||||
|
let mut kdl_doucment = KdlDocument::new();
|
||||||
|
for (tab_position, panes) in &self.panes {
|
||||||
|
for pane in panes {
|
||||||
|
let mut pane_node = KdlNode::new("pane");
|
||||||
|
let mut pane = pane.encode_to_kdl();
|
||||||
|
|
||||||
|
let mut position_node = KdlNode::new("tab_position");
|
||||||
|
position_node.push(*tab_position as i64);
|
||||||
|
pane.nodes_mut().push(position_node);
|
||||||
|
|
||||||
|
pane_node.set_children(pane);
|
||||||
|
kdl_doucment.nodes_mut().push(pane_node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
kdl_doucment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PaneInfo {
|
||||||
|
pub fn decode_from_kdl(kdl_document: &KdlDocument) -> Result<(usize, Self), String> {
|
||||||
|
// usize is the tab position
|
||||||
|
macro_rules! int_node {
|
||||||
|
($name:expr, $type:ident) => {{
|
||||||
|
kdl_document
|
||||||
|
.get($name)
|
||||||
|
.and_then(|n| n.entries().iter().next())
|
||||||
|
.and_then(|e| e.value().as_i64())
|
||||||
|
.map(|e| e as $type)
|
||||||
|
.ok_or(format!("Failed to parse pane {}", $name))?
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
macro_rules! optional_int_node {
|
||||||
|
($name:expr, $type:ident) => {{
|
||||||
|
kdl_document
|
||||||
|
.get($name)
|
||||||
|
.and_then(|n| n.entries().iter().next())
|
||||||
|
.and_then(|e| e.value().as_i64())
|
||||||
|
.map(|e| e as $type)
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
macro_rules! bool_node {
|
||||||
|
($name:expr) => {{
|
||||||
|
kdl_document
|
||||||
|
.get($name)
|
||||||
|
.and_then(|n| n.entries().iter().next())
|
||||||
|
.and_then(|e| e.value().as_bool())
|
||||||
|
.ok_or(format!("Failed to parse pane {}", $name))?
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
macro_rules! string_node {
|
||||||
|
($name:expr) => {{
|
||||||
|
kdl_document
|
||||||
|
.get($name)
|
||||||
|
.and_then(|n| n.entries().iter().next())
|
||||||
|
.and_then(|e| e.value().as_string())
|
||||||
|
.map(|s| s.to_owned())
|
||||||
|
.ok_or(format!("Failed to parse pane {}", $name))?
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
macro_rules! optional_string_node {
|
||||||
|
($name:expr) => {{
|
||||||
|
kdl_document
|
||||||
|
.get($name)
|
||||||
|
.and_then(|n| n.entries().iter().next())
|
||||||
|
.and_then(|e| e.value().as_string())
|
||||||
|
.map(|s| s.to_owned())
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
let tab_position = int_node!("tab_position", usize);
|
||||||
|
let id = int_node!("id", u32);
|
||||||
|
|
||||||
|
let is_plugin = bool_node!("is_plugin");
|
||||||
|
let is_focused = bool_node!("is_focused");
|
||||||
|
let is_fullscreen = bool_node!("is_fullscreen");
|
||||||
|
let is_floating = bool_node!("is_floating");
|
||||||
|
let is_suppressed = bool_node!("is_suppressed");
|
||||||
|
let title = string_node!("title");
|
||||||
|
let exited = bool_node!("exited");
|
||||||
|
let exit_status = optional_int_node!("exit_status", i32);
|
||||||
|
let is_held = bool_node!("is_held");
|
||||||
|
let pane_x = int_node!("pane_x", usize);
|
||||||
|
let pane_content_x = int_node!("pane_content_x", usize);
|
||||||
|
let pane_y = int_node!("pane_y", usize);
|
||||||
|
let pane_content_y = int_node!("pane_content_y", usize);
|
||||||
|
let pane_rows = int_node!("pane_rows", usize);
|
||||||
|
let pane_content_rows = int_node!("pane_content_rows", usize);
|
||||||
|
let pane_columns = int_node!("pane_columns", usize);
|
||||||
|
let pane_content_columns = int_node!("pane_content_columns", usize);
|
||||||
|
let cursor_coordinates_in_pane = kdl_document
|
||||||
|
.get("cursor_coordinates_in_pane")
|
||||||
|
.map(|n| {
|
||||||
|
let mut entries = n.entries().iter();
|
||||||
|
(entries.next(), entries.next())
|
||||||
|
})
|
||||||
|
.and_then(|(x, y)| {
|
||||||
|
let x = x.and_then(|x| x.value().as_i64()).map(|x| x as usize);
|
||||||
|
let y = y.and_then(|y| y.value().as_i64()).map(|y| y as usize);
|
||||||
|
match (x, y) {
|
||||||
|
(Some(x), Some(y)) => Some((x, y)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let terminal_command = optional_string_node!("terminal_command");
|
||||||
|
let plugin_url = optional_string_node!("plugin_url");
|
||||||
|
let is_selectable = bool_node!("is_selectable");
|
||||||
|
|
||||||
|
let pane_info = PaneInfo {
|
||||||
|
id,
|
||||||
|
is_plugin,
|
||||||
|
is_focused,
|
||||||
|
is_fullscreen,
|
||||||
|
is_floating,
|
||||||
|
is_suppressed,
|
||||||
|
title,
|
||||||
|
exited,
|
||||||
|
exit_status,
|
||||||
|
is_held,
|
||||||
|
pane_x,
|
||||||
|
pane_content_x,
|
||||||
|
pane_y,
|
||||||
|
pane_content_y,
|
||||||
|
pane_rows,
|
||||||
|
pane_content_rows,
|
||||||
|
pane_columns,
|
||||||
|
pane_content_columns,
|
||||||
|
cursor_coordinates_in_pane,
|
||||||
|
terminal_command,
|
||||||
|
plugin_url,
|
||||||
|
is_selectable,
|
||||||
|
};
|
||||||
|
Ok((tab_position, pane_info))
|
||||||
|
}
|
||||||
|
pub fn encode_to_kdl(&self) -> KdlDocument {
|
||||||
|
let mut kdl_doucment = KdlDocument::new();
|
||||||
|
macro_rules! int_node {
|
||||||
|
($name:expr, $val:expr) => {{
|
||||||
|
let mut att = KdlNode::new($name);
|
||||||
|
att.push($val as i64);
|
||||||
|
kdl_doucment.nodes_mut().push(att);
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
macro_rules! bool_node {
|
||||||
|
($name:expr, $val:expr) => {{
|
||||||
|
let mut att = KdlNode::new($name);
|
||||||
|
att.push($val);
|
||||||
|
kdl_doucment.nodes_mut().push(att);
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
macro_rules! string_node {
|
||||||
|
($name:expr, $val:expr) => {{
|
||||||
|
let mut att = KdlNode::new($name);
|
||||||
|
att.push($val);
|
||||||
|
kdl_doucment.nodes_mut().push(att);
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
int_node!("id", self.id);
|
||||||
|
bool_node!("is_plugin", self.is_plugin);
|
||||||
|
bool_node!("is_focused", self.is_focused);
|
||||||
|
bool_node!("is_fullscreen", self.is_fullscreen);
|
||||||
|
bool_node!("is_floating", self.is_floating);
|
||||||
|
bool_node!("is_suppressed", self.is_suppressed);
|
||||||
|
string_node!("title", self.title.to_string());
|
||||||
|
bool_node!("exited", self.exited);
|
||||||
|
if let Some(exit_status) = self.exit_status {
|
||||||
|
int_node!("exit_status", exit_status);
|
||||||
|
}
|
||||||
|
bool_node!("is_held", self.is_held);
|
||||||
|
int_node!("pane_x", self.pane_x);
|
||||||
|
int_node!("pane_content_x", self.pane_content_x);
|
||||||
|
int_node!("pane_y", self.pane_y);
|
||||||
|
int_node!("pane_content_y", self.pane_content_y);
|
||||||
|
int_node!("pane_rows", self.pane_rows);
|
||||||
|
int_node!("pane_content_rows", self.pane_content_rows);
|
||||||
|
int_node!("pane_columns", self.pane_columns);
|
||||||
|
int_node!("pane_content_columns", self.pane_content_columns);
|
||||||
|
if let Some((cursor_x, cursor_y)) = self.cursor_coordinates_in_pane {
|
||||||
|
let mut cursor_coordinates_in_pane = KdlNode::new("cursor_coordinates_in_pane");
|
||||||
|
cursor_coordinates_in_pane.push(cursor_x as i64);
|
||||||
|
cursor_coordinates_in_pane.push(cursor_y as i64);
|
||||||
|
kdl_doucment.nodes_mut().push(cursor_coordinates_in_pane);
|
||||||
|
}
|
||||||
|
if let Some(terminal_command) = &self.terminal_command {
|
||||||
|
string_node!("terminal_command", terminal_command.to_string());
|
||||||
|
}
|
||||||
|
if let Some(plugin_url) = &self.plugin_url {
|
||||||
|
string_node!("plugin_url", plugin_url.to_string());
|
||||||
|
}
|
||||||
|
bool_node!("is_selectable", self.is_selectable);
|
||||||
|
kdl_doucment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_plugin_user_configuration(
|
pub fn parse_plugin_user_configuration(
|
||||||
plugin_block: &KdlNode,
|
plugin_block: &KdlNode,
|
||||||
) -> Result<BTreeMap<String, String>, ConfigError> {
|
) -> Result<BTreeMap<String, String>, ConfigError> {
|
||||||
|
|
@ -1888,3 +2303,104 @@ pub fn parse_plugin_user_configuration(
|
||||||
}
|
}
|
||||||
Ok(configuration)
|
Ok(configuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialize_and_deserialize_session_info() {
|
||||||
|
let session_info = SessionInfo::default();
|
||||||
|
let serialized = session_info.to_string();
|
||||||
|
let deserealized = SessionInfo::from_string(&serialized, "not this session").unwrap();
|
||||||
|
assert_eq!(session_info, deserealized);
|
||||||
|
insta::assert_snapshot!(serialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialize_and_deserialize_session_info_with_data() {
|
||||||
|
let panes_list = vec![
|
||||||
|
PaneInfo {
|
||||||
|
id: 1,
|
||||||
|
is_plugin: false,
|
||||||
|
is_focused: true,
|
||||||
|
is_fullscreen: true,
|
||||||
|
is_floating: false,
|
||||||
|
is_suppressed: false,
|
||||||
|
title: "pane 1".to_owned(),
|
||||||
|
exited: false,
|
||||||
|
exit_status: None,
|
||||||
|
is_held: false,
|
||||||
|
pane_x: 0,
|
||||||
|
pane_content_x: 1,
|
||||||
|
pane_y: 0,
|
||||||
|
pane_content_y: 1,
|
||||||
|
pane_rows: 5,
|
||||||
|
pane_content_rows: 4,
|
||||||
|
pane_columns: 22,
|
||||||
|
pane_content_columns: 21,
|
||||||
|
cursor_coordinates_in_pane: Some((0, 0)),
|
||||||
|
terminal_command: Some("foo".to_owned()),
|
||||||
|
plugin_url: None,
|
||||||
|
is_selectable: true,
|
||||||
|
},
|
||||||
|
PaneInfo {
|
||||||
|
id: 1,
|
||||||
|
is_plugin: true,
|
||||||
|
is_focused: true,
|
||||||
|
is_fullscreen: true,
|
||||||
|
is_floating: false,
|
||||||
|
is_suppressed: false,
|
||||||
|
title: "pane 1".to_owned(),
|
||||||
|
exited: false,
|
||||||
|
exit_status: None,
|
||||||
|
is_held: false,
|
||||||
|
pane_x: 0,
|
||||||
|
pane_content_x: 1,
|
||||||
|
pane_y: 0,
|
||||||
|
pane_content_y: 1,
|
||||||
|
pane_rows: 5,
|
||||||
|
pane_content_rows: 4,
|
||||||
|
pane_columns: 22,
|
||||||
|
pane_content_columns: 21,
|
||||||
|
cursor_coordinates_in_pane: Some((0, 0)),
|
||||||
|
terminal_command: None,
|
||||||
|
plugin_url: Some("i_am_a_fake_plugin".to_owned()),
|
||||||
|
is_selectable: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let mut panes = HashMap::new();
|
||||||
|
panes.insert(0, panes_list);
|
||||||
|
let session_info = SessionInfo {
|
||||||
|
name: "my session name".to_owned(),
|
||||||
|
tabs: vec![
|
||||||
|
TabInfo {
|
||||||
|
position: 0,
|
||||||
|
name: "tab 1".to_owned(),
|
||||||
|
active: true,
|
||||||
|
panes_to_hide: 1,
|
||||||
|
is_fullscreen_active: true,
|
||||||
|
is_sync_panes_active: false,
|
||||||
|
are_floating_panes_visible: true,
|
||||||
|
other_focused_clients: vec![2, 3],
|
||||||
|
active_swap_layout_name: Some("BASE".to_owned()),
|
||||||
|
is_swap_layout_dirty: true,
|
||||||
|
},
|
||||||
|
TabInfo {
|
||||||
|
position: 1,
|
||||||
|
name: "tab 2".to_owned(),
|
||||||
|
active: true,
|
||||||
|
panes_to_hide: 0,
|
||||||
|
is_fullscreen_active: false,
|
||||||
|
is_sync_panes_active: true,
|
||||||
|
are_floating_panes_visible: true,
|
||||||
|
other_focused_clients: vec![2, 3],
|
||||||
|
active_swap_layout_name: None,
|
||||||
|
is_swap_layout_dirty: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
panes: PaneManifest { panes },
|
||||||
|
connected_clients: 2,
|
||||||
|
is_current_session: false,
|
||||||
|
};
|
||||||
|
let serialized = session_info.to_string();
|
||||||
|
let deserealized = SessionInfo::from_string(&serialized, "not this session").unwrap();
|
||||||
|
assert_eq!(session_info, deserealized);
|
||||||
|
insta::assert_snapshot!(serialized);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
---
|
||||||
|
source: zellij-utils/src/kdl/mod.rs
|
||||||
|
assertion_line: 2284
|
||||||
|
expression: serialized
|
||||||
|
---
|
||||||
|
name ""
|
||||||
|
tabs {
|
||||||
|
}
|
||||||
|
panes {
|
||||||
|
}
|
||||||
|
connected_clients 0
|
||||||
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
---
|
||||||
|
source: zellij-utils/src/kdl/mod.rs
|
||||||
|
assertion_line: 2377
|
||||||
|
expression: serialized
|
||||||
|
---
|
||||||
|
name "my session name"
|
||||||
|
tabs {
|
||||||
|
tab {
|
||||||
|
position 0
|
||||||
|
name "tab 1"
|
||||||
|
active true
|
||||||
|
panes_to_hide 1
|
||||||
|
is_fullscreen_active true
|
||||||
|
is_sync_panes_active false
|
||||||
|
are_floating_panes_visible true
|
||||||
|
other_focused_clients 2 3
|
||||||
|
active_swap_layout_name "BASE"
|
||||||
|
is_swap_layout_dirty true
|
||||||
|
}
|
||||||
|
tab {
|
||||||
|
position 1
|
||||||
|
name "tab 2"
|
||||||
|
active true
|
||||||
|
panes_to_hide 0
|
||||||
|
is_fullscreen_active false
|
||||||
|
is_sync_panes_active true
|
||||||
|
are_floating_panes_visible true
|
||||||
|
other_focused_clients 2 3
|
||||||
|
is_swap_layout_dirty false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panes {
|
||||||
|
pane {
|
||||||
|
id 1
|
||||||
|
is_plugin false
|
||||||
|
is_focused true
|
||||||
|
is_fullscreen true
|
||||||
|
is_floating false
|
||||||
|
is_suppressed false
|
||||||
|
title "pane 1"
|
||||||
|
exited false
|
||||||
|
is_held false
|
||||||
|
pane_x 0
|
||||||
|
pane_content_x 1
|
||||||
|
pane_y 0
|
||||||
|
pane_content_y 1
|
||||||
|
pane_rows 5
|
||||||
|
pane_content_rows 4
|
||||||
|
pane_columns 22
|
||||||
|
pane_content_columns 21
|
||||||
|
cursor_coordinates_in_pane 0 0
|
||||||
|
terminal_command "foo"
|
||||||
|
is_selectable true
|
||||||
|
tab_position 0
|
||||||
|
}
|
||||||
|
pane {
|
||||||
|
id 1
|
||||||
|
is_plugin true
|
||||||
|
is_focused true
|
||||||
|
is_fullscreen true
|
||||||
|
is_floating false
|
||||||
|
is_suppressed false
|
||||||
|
title "pane 1"
|
||||||
|
exited false
|
||||||
|
is_held false
|
||||||
|
pane_x 0
|
||||||
|
pane_content_x 1
|
||||||
|
pane_y 0
|
||||||
|
pane_content_y 1
|
||||||
|
pane_rows 5
|
||||||
|
pane_content_rows 4
|
||||||
|
pane_columns 22
|
||||||
|
pane_content_columns 21
|
||||||
|
cursor_coordinates_in_pane 0 0
|
||||||
|
plugin_url "i_am_a_fake_plugin"
|
||||||
|
is_selectable true
|
||||||
|
tab_position 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
connected_clients 2
|
||||||
|
|
||||||
|
|
@ -39,6 +39,7 @@ enum EventType {
|
||||||
/// A file was deleted somewhere in the Zellij CWD folder
|
/// A file was deleted somewhere in the Zellij CWD folder
|
||||||
FileSystemDelete = 14;
|
FileSystemDelete = 14;
|
||||||
PermissionRequestResult = 15;
|
PermissionRequestResult = 15;
|
||||||
|
SessionUpdate = 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
message EventNameList {
|
message EventNameList {
|
||||||
|
|
@ -59,9 +60,14 @@ message Event {
|
||||||
CustomMessagePayload custom_message_payload = 10;
|
CustomMessagePayload custom_message_payload = 10;
|
||||||
FileListPayload file_list_payload = 11;
|
FileListPayload file_list_payload = 11;
|
||||||
PermissionRequestResultPayload permission_request_result_payload = 12;
|
PermissionRequestResultPayload permission_request_result_payload = 12;
|
||||||
|
SessionUpdatePayload session_update_payload = 13;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message SessionUpdatePayload {
|
||||||
|
repeated SessionManifest session_manifests = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message PermissionRequestResultPayload {
|
message PermissionRequestResultPayload {
|
||||||
bool granted = 1;
|
bool granted = 1;
|
||||||
}
|
}
|
||||||
|
|
@ -111,6 +117,14 @@ message PaneManifest {
|
||||||
repeated PaneInfo panes = 2;
|
repeated PaneInfo panes = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message SessionManifest {
|
||||||
|
string name = 1;
|
||||||
|
repeated TabInfo tabs = 2;
|
||||||
|
repeated PaneManifest panes = 3;
|
||||||
|
uint32 connected_clients = 4;
|
||||||
|
bool is_current_session = 5;
|
||||||
|
}
|
||||||
|
|
||||||
message PaneInfo {
|
message PaneInfo {
|
||||||
uint32 id = 1;
|
uint32 id = 1;
|
||||||
bool is_plugin = 2;
|
bool is_plugin = 2;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ pub use super::generated_api::api::{
|
||||||
EventType as ProtobufEventType, InputModeKeybinds as ProtobufInputModeKeybinds,
|
EventType as ProtobufEventType, InputModeKeybinds as ProtobufInputModeKeybinds,
|
||||||
KeyBind as ProtobufKeyBind, ModeUpdatePayload as ProtobufModeUpdatePayload,
|
KeyBind as ProtobufKeyBind, ModeUpdatePayload as ProtobufModeUpdatePayload,
|
||||||
PaneInfo as ProtobufPaneInfo, PaneManifest as ProtobufPaneManifest,
|
PaneInfo as ProtobufPaneInfo, PaneManifest as ProtobufPaneManifest,
|
||||||
TabInfo as ProtobufTabInfo, *,
|
SessionManifest as ProtobufSessionManifest, TabInfo as ProtobufTabInfo, *,
|
||||||
},
|
},
|
||||||
input_mode::InputMode as ProtobufInputMode,
|
input_mode::InputMode as ProtobufInputMode,
|
||||||
key::Key as ProtobufKey,
|
key::Key as ProtobufKey,
|
||||||
|
|
@ -14,7 +14,7 @@ pub use super::generated_api::api::{
|
||||||
};
|
};
|
||||||
use crate::data::{
|
use crate::data::{
|
||||||
CopyDestination, Event, EventType, InputMode, Key, ModeInfo, Mouse, PaneInfo, PaneManifest,
|
CopyDestination, Event, EventType, InputMode, Key, ModeInfo, Mouse, PaneInfo, PaneManifest,
|
||||||
PermissionStatus, PluginCapabilities, Style, TabInfo,
|
PermissionStatus, PluginCapabilities, SessionInfo, Style, TabInfo,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::errors::prelude::*;
|
use crate::errors::prelude::*;
|
||||||
|
|
@ -171,6 +171,18 @@ impl TryFrom<ProtobufEvent> for Event {
|
||||||
},
|
},
|
||||||
_ => Err("Malformed payload for the file system delete Event"),
|
_ => Err("Malformed payload for the file system delete Event"),
|
||||||
},
|
},
|
||||||
|
Some(ProtobufEventType::SessionUpdate) => match protobuf_event.payload {
|
||||||
|
Some(ProtobufEventPayload::SessionUpdatePayload(
|
||||||
|
protobuf_session_update_payload,
|
||||||
|
)) => {
|
||||||
|
let mut session_infos: Vec<SessionInfo> = vec![];
|
||||||
|
for protobuf_session_info in protobuf_session_update_payload.session_manifests {
|
||||||
|
session_infos.push(SessionInfo::try_from(protobuf_session_info)?);
|
||||||
|
}
|
||||||
|
Ok(Event::SessionUpdate(session_infos))
|
||||||
|
},
|
||||||
|
_ => Err("Malformed payload for the SessionUpdate Event"),
|
||||||
|
},
|
||||||
None => Err("Unknown Protobuf Event"),
|
None => Err("Unknown Protobuf Event"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -313,10 +325,83 @@ impl TryFrom<Event> for ProtobufEvent {
|
||||||
)),
|
)),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
Event::SessionUpdate(session_infos) => {
|
||||||
|
let mut protobuf_session_manifests = vec![];
|
||||||
|
for session_info in session_infos {
|
||||||
|
protobuf_session_manifests.push(session_info.try_into()?);
|
||||||
|
}
|
||||||
|
let session_update_payload = SessionUpdatePayload {
|
||||||
|
session_manifests: protobuf_session_manifests,
|
||||||
|
};
|
||||||
|
Ok(ProtobufEvent {
|
||||||
|
name: ProtobufEventType::SessionUpdate as i32,
|
||||||
|
payload: Some(event::Payload::SessionUpdatePayload(session_update_payload)),
|
||||||
|
})
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<SessionInfo> for ProtobufSessionManifest {
|
||||||
|
type Error = &'static str;
|
||||||
|
fn try_from(session_info: SessionInfo) -> Result<Self, &'static str> {
|
||||||
|
let mut protobuf_pane_manifests = vec![];
|
||||||
|
for (tab_index, pane_infos) in session_info.panes.panes {
|
||||||
|
let mut protobuf_pane_infos = vec![];
|
||||||
|
for pane_info in pane_infos {
|
||||||
|
protobuf_pane_infos.push(pane_info.try_into()?);
|
||||||
|
}
|
||||||
|
protobuf_pane_manifests.push(ProtobufPaneManifest {
|
||||||
|
tab_index: tab_index as u32,
|
||||||
|
panes: protobuf_pane_infos,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(ProtobufSessionManifest {
|
||||||
|
name: session_info.name,
|
||||||
|
panes: protobuf_pane_manifests,
|
||||||
|
tabs: session_info
|
||||||
|
.tabs
|
||||||
|
.iter()
|
||||||
|
.filter_map(|t| t.clone().try_into().ok())
|
||||||
|
.collect(),
|
||||||
|
connected_clients: session_info.connected_clients as u32,
|
||||||
|
is_current_session: session_info.is_current_session,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<ProtobufSessionManifest> for SessionInfo {
|
||||||
|
type Error = &'static str;
|
||||||
|
fn try_from(protobuf_session_manifest: ProtobufSessionManifest) -> Result<Self, &'static str> {
|
||||||
|
let mut pane_manifest: HashMap<usize, Vec<PaneInfo>> = HashMap::new();
|
||||||
|
for protobuf_pane_manifest in protobuf_session_manifest.panes {
|
||||||
|
let tab_index = protobuf_pane_manifest.tab_index as usize;
|
||||||
|
let mut panes = vec![];
|
||||||
|
for protobuf_pane_info in protobuf_pane_manifest.panes {
|
||||||
|
panes.push(protobuf_pane_info.try_into()?);
|
||||||
|
}
|
||||||
|
if pane_manifest.contains_key(&tab_index) {
|
||||||
|
return Err("Duplicate tab definition in pane manifest");
|
||||||
|
}
|
||||||
|
pane_manifest.insert(tab_index, panes);
|
||||||
|
}
|
||||||
|
let panes = PaneManifest {
|
||||||
|
panes: pane_manifest,
|
||||||
|
};
|
||||||
|
Ok(SessionInfo {
|
||||||
|
name: protobuf_session_manifest.name,
|
||||||
|
tabs: protobuf_session_manifest
|
||||||
|
.tabs
|
||||||
|
.iter()
|
||||||
|
.filter_map(|t| t.clone().try_into().ok())
|
||||||
|
.collect(),
|
||||||
|
panes,
|
||||||
|
connected_clients: protobuf_session_manifest.connected_clients as usize,
|
||||||
|
is_current_session: protobuf_session_manifest.is_current_session,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<CopyDestination> for ProtobufCopyDestination {
|
impl TryFrom<CopyDestination> for ProtobufCopyDestination {
|
||||||
type Error = &'static str;
|
type Error = &'static str;
|
||||||
fn try_from(copy_destination: CopyDestination) -> Result<Self, &'static str> {
|
fn try_from(copy_destination: CopyDestination) -> Result<Self, &'static str> {
|
||||||
|
|
@ -697,6 +782,7 @@ impl TryFrom<ProtobufEventType> for EventType {
|
||||||
ProtobufEventType::FileSystemUpdate => EventType::FileSystemUpdate,
|
ProtobufEventType::FileSystemUpdate => EventType::FileSystemUpdate,
|
||||||
ProtobufEventType::FileSystemDelete => EventType::FileSystemDelete,
|
ProtobufEventType::FileSystemDelete => EventType::FileSystemDelete,
|
||||||
ProtobufEventType::PermissionRequestResult => EventType::PermissionRequestResult,
|
ProtobufEventType::PermissionRequestResult => EventType::PermissionRequestResult,
|
||||||
|
ProtobufEventType::SessionUpdate => EventType::SessionUpdate,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -721,6 +807,7 @@ impl TryFrom<EventType> for ProtobufEventType {
|
||||||
EventType::FileSystemUpdate => ProtobufEventType::FileSystemUpdate,
|
EventType::FileSystemUpdate => ProtobufEventType::FileSystemUpdate,
|
||||||
EventType::FileSystemDelete => ProtobufEventType::FileSystemDelete,
|
EventType::FileSystemDelete => ProtobufEventType::FileSystemDelete,
|
||||||
EventType::PermissionRequestResult => ProtobufEventType::PermissionRequestResult,
|
EventType::PermissionRequestResult => ProtobufEventType::PermissionRequestResult,
|
||||||
|
EventType::SessionUpdate => ProtobufEventType::SessionUpdate,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1083,3 +1170,130 @@ fn serialize_file_system_delete_event() {
|
||||||
"Event properly serialized/deserialized without change"
|
"Event properly serialized/deserialized without change"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialize_session_update_event() {
|
||||||
|
use prost::Message;
|
||||||
|
let session_update_event = Event::SessionUpdate(Default::default());
|
||||||
|
let protobuf_event: ProtobufEvent = session_update_event.clone().try_into().unwrap();
|
||||||
|
let serialized_protobuf_event = protobuf_event.encode_to_vec();
|
||||||
|
let deserialized_protobuf_event: ProtobufEvent =
|
||||||
|
Message::decode(serialized_protobuf_event.as_slice()).unwrap();
|
||||||
|
let deserialized_event: Event = deserialized_protobuf_event.try_into().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
session_update_event, deserialized_event,
|
||||||
|
"Event properly serialized/deserialized without change"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialize_session_update_event_with_non_default_values() {
|
||||||
|
use prost::Message;
|
||||||
|
let tab_infos = vec![
|
||||||
|
TabInfo {
|
||||||
|
position: 0,
|
||||||
|
name: "First tab".to_owned(),
|
||||||
|
active: true,
|
||||||
|
panes_to_hide: 2,
|
||||||
|
is_fullscreen_active: true,
|
||||||
|
is_sync_panes_active: false,
|
||||||
|
are_floating_panes_visible: true,
|
||||||
|
other_focused_clients: vec![2, 3, 4],
|
||||||
|
active_swap_layout_name: Some("my cool swap layout".to_owned()),
|
||||||
|
is_swap_layout_dirty: false,
|
||||||
|
},
|
||||||
|
TabInfo {
|
||||||
|
position: 1,
|
||||||
|
name: "Secondtab".to_owned(),
|
||||||
|
active: false,
|
||||||
|
panes_to_hide: 5,
|
||||||
|
is_fullscreen_active: false,
|
||||||
|
is_sync_panes_active: true,
|
||||||
|
are_floating_panes_visible: true,
|
||||||
|
other_focused_clients: vec![1, 5, 111],
|
||||||
|
active_swap_layout_name: None,
|
||||||
|
is_swap_layout_dirty: true,
|
||||||
|
},
|
||||||
|
TabInfo::default(),
|
||||||
|
];
|
||||||
|
let mut panes = HashMap::new();
|
||||||
|
let panes_list = vec![
|
||||||
|
PaneInfo {
|
||||||
|
id: 1,
|
||||||
|
is_plugin: false,
|
||||||
|
is_focused: true,
|
||||||
|
is_fullscreen: true,
|
||||||
|
is_floating: false,
|
||||||
|
is_suppressed: false,
|
||||||
|
title: "pane 1".to_owned(),
|
||||||
|
exited: false,
|
||||||
|
exit_status: None,
|
||||||
|
is_held: false,
|
||||||
|
pane_x: 0,
|
||||||
|
pane_content_x: 1,
|
||||||
|
pane_y: 0,
|
||||||
|
pane_content_y: 1,
|
||||||
|
pane_rows: 5,
|
||||||
|
pane_content_rows: 4,
|
||||||
|
pane_columns: 22,
|
||||||
|
pane_content_columns: 21,
|
||||||
|
cursor_coordinates_in_pane: Some((0, 0)),
|
||||||
|
terminal_command: Some("foo".to_owned()),
|
||||||
|
plugin_url: None,
|
||||||
|
is_selectable: true,
|
||||||
|
},
|
||||||
|
PaneInfo {
|
||||||
|
id: 1,
|
||||||
|
is_plugin: true,
|
||||||
|
is_focused: true,
|
||||||
|
is_fullscreen: true,
|
||||||
|
is_floating: false,
|
||||||
|
is_suppressed: false,
|
||||||
|
title: "pane 1".to_owned(),
|
||||||
|
exited: false,
|
||||||
|
exit_status: None,
|
||||||
|
is_held: false,
|
||||||
|
pane_x: 0,
|
||||||
|
pane_content_x: 1,
|
||||||
|
pane_y: 0,
|
||||||
|
pane_content_y: 1,
|
||||||
|
pane_rows: 5,
|
||||||
|
pane_content_rows: 4,
|
||||||
|
pane_columns: 22,
|
||||||
|
pane_content_columns: 21,
|
||||||
|
cursor_coordinates_in_pane: Some((0, 0)),
|
||||||
|
terminal_command: None,
|
||||||
|
plugin_url: Some("i_am_a_fake_plugin".to_owned()),
|
||||||
|
is_selectable: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
panes.insert(0, panes_list);
|
||||||
|
let session_info_1 = SessionInfo {
|
||||||
|
name: "session 1".to_owned(),
|
||||||
|
tabs: tab_infos,
|
||||||
|
panes: PaneManifest { panes },
|
||||||
|
connected_clients: 2,
|
||||||
|
is_current_session: true,
|
||||||
|
};
|
||||||
|
let session_info_2 = SessionInfo {
|
||||||
|
name: "session 2".to_owned(),
|
||||||
|
tabs: vec![],
|
||||||
|
panes: PaneManifest {
|
||||||
|
panes: HashMap::new(),
|
||||||
|
},
|
||||||
|
connected_clients: 0,
|
||||||
|
is_current_session: false,
|
||||||
|
};
|
||||||
|
let session_infos = vec![session_info_1, session_info_2];
|
||||||
|
|
||||||
|
let session_update_event = Event::SessionUpdate(session_infos);
|
||||||
|
let protobuf_event: ProtobufEvent = session_update_event.clone().try_into().unwrap();
|
||||||
|
let serialized_protobuf_event = protobuf_event.encode_to_vec();
|
||||||
|
let deserialized_protobuf_event: ProtobufEvent =
|
||||||
|
Message::decode(serialized_protobuf_event.as_slice()).unwrap();
|
||||||
|
let deserialized_event: Event = deserialized_protobuf_event.try_into().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
session_update_event, deserialized_event,
|
||||||
|
"Event properly serialized/deserialized without change"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,7 @@ enum CommandName {
|
||||||
RenameTab = 64;
|
RenameTab = 64;
|
||||||
ReportCrash = 65;
|
ReportCrash = 65;
|
||||||
RequestPluginPermissions = 66;
|
RequestPluginPermissions = 66;
|
||||||
|
SwitchSession = 67;
|
||||||
}
|
}
|
||||||
|
|
||||||
message PluginCommand {
|
message PluginCommand {
|
||||||
|
|
@ -120,9 +121,17 @@ message PluginCommand {
|
||||||
IdAndNewName rename_tab_payload = 36;
|
IdAndNewName rename_tab_payload = 36;
|
||||||
string report_crash_payload = 37;
|
string report_crash_payload = 37;
|
||||||
RequestPluginPermissionPayload request_plugin_permission_payload = 38;
|
RequestPluginPermissionPayload request_plugin_permission_payload = 38;
|
||||||
|
SwitchSessionPayload switch_session_payload = 39;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message SwitchSessionPayload {
|
||||||
|
optional string name = 1;
|
||||||
|
optional uint32 tab_position = 2;
|
||||||
|
optional uint32 pane_id = 3;
|
||||||
|
optional bool pane_id_is_plugin = 4;
|
||||||
|
}
|
||||||
|
|
||||||
message RequestPluginPermissionPayload {
|
message RequestPluginPermissionPayload {
|
||||||
repeated plugin_permission.PermissionType permissions = 1;
|
repeated plugin_permission.PermissionType permissions = 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,13 @@ pub use super::generated_api::api::{
|
||||||
OpenCommandPanePayload, OpenFilePayload, PaneIdAndShouldFloat,
|
OpenCommandPanePayload, OpenFilePayload, PaneIdAndShouldFloat,
|
||||||
PluginCommand as ProtobufPluginCommand, PluginMessagePayload,
|
PluginCommand as ProtobufPluginCommand, PluginMessagePayload,
|
||||||
RequestPluginPermissionPayload, ResizePayload, SetTimeoutPayload, SubscribePayload,
|
RequestPluginPermissionPayload, ResizePayload, SetTimeoutPayload, SubscribePayload,
|
||||||
SwitchTabToPayload, SwitchToModePayload, UnsubscribePayload,
|
SwitchSessionPayload, SwitchTabToPayload, SwitchToModePayload, UnsubscribePayload,
|
||||||
},
|
},
|
||||||
plugin_permission::PermissionType as ProtobufPermissionType,
|
plugin_permission::PermissionType as ProtobufPermissionType,
|
||||||
resize::ResizeAction as ProtobufResizeAction,
|
resize::ResizeAction as ProtobufResizeAction,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::data::{PermissionType, PluginCommand};
|
use crate::data::{ConnectToSession, PermissionType, PluginCommand};
|
||||||
|
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
|
@ -500,6 +500,23 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
|
||||||
},
|
},
|
||||||
_ => Err("Mismatched payload for RequestPluginPermission"),
|
_ => Err("Mismatched payload for RequestPluginPermission"),
|
||||||
},
|
},
|
||||||
|
Some(CommandName::SwitchSession) => match protobuf_plugin_command.payload {
|
||||||
|
Some(Payload::SwitchSessionPayload(payload)) => {
|
||||||
|
let pane_id = match (payload.pane_id, payload.pane_id_is_plugin) {
|
||||||
|
(Some(pane_id), Some(is_plugin)) => Some((pane_id, is_plugin)),
|
||||||
|
(None, None) => None,
|
||||||
|
_ => {
|
||||||
|
return Err("Malformed payload for SwitchSession, 'pane_id' and 'is_plugin' must be included together or not at all")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(PluginCommand::SwitchSession(ConnectToSession {
|
||||||
|
name: payload.name,
|
||||||
|
tab_position: payload.tab_position.map(|p| p as usize),
|
||||||
|
pane_id,
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
_ => Err("Mismatched payload for SwitchSession"),
|
||||||
|
},
|
||||||
None => Err("Unrecognized plugin command"),
|
None => Err("Unrecognized plugin command"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -846,6 +863,15 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
}),
|
}),
|
||||||
|
PluginCommand::SwitchSession(switch_to_session) => Ok(ProtobufPluginCommand {
|
||||||
|
name: CommandName::SwitchSession as i32,
|
||||||
|
payload: Some(Payload::SwitchSessionPayload(SwitchSessionPayload {
|
||||||
|
name: switch_to_session.name,
|
||||||
|
tab_position: switch_to_session.tab_position.map(|t| t as u32),
|
||||||
|
pane_id: switch_to_session.pane_id.map(|p| p.0),
|
||||||
|
pane_id_is_plugin: switch_to_session.pane_id.map(|p| p.1),
|
||||||
|
})),
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2489,6 +2489,29 @@ Config {
|
||||||
): [
|
): [
|
||||||
Detach,
|
Detach,
|
||||||
],
|
],
|
||||||
|
Char(
|
||||||
|
'w',
|
||||||
|
): [
|
||||||
|
LaunchOrFocusPlugin(
|
||||||
|
RunPlugin {
|
||||||
|
_allow_exec_host_cmd: false,
|
||||||
|
location: Zellij(
|
||||||
|
PluginTag(
|
||||||
|
"session-manager",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
configuration: PluginUserConfiguration(
|
||||||
|
{
|
||||||
|
"floating": "true",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
SwitchToMode(
|
||||||
|
Normal,
|
||||||
|
),
|
||||||
|
],
|
||||||
Alt(
|
Alt(
|
||||||
Char(
|
Char(
|
||||||
'+',
|
'+',
|
||||||
|
|
@ -3586,6 +3609,23 @@ Config {
|
||||||
{},
|
{},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
PluginTag(
|
||||||
|
"session-manager",
|
||||||
|
): PluginConfig {
|
||||||
|
path: "session-manager",
|
||||||
|
run: Pane(
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
_allow_exec_host_cmd: false,
|
||||||
|
location: Zellij(
|
||||||
|
PluginTag(
|
||||||
|
"session-manager",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
userspace_configuration: PluginUserConfiguration(
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
},
|
||||||
PluginTag(
|
PluginTag(
|
||||||
"status-bar",
|
"status-bar",
|
||||||
): PluginConfig {
|
): PluginConfig {
|
||||||
|
|
|
||||||
|
|
@ -2489,6 +2489,29 @@ Config {
|
||||||
): [
|
): [
|
||||||
Detach,
|
Detach,
|
||||||
],
|
],
|
||||||
|
Char(
|
||||||
|
'w',
|
||||||
|
): [
|
||||||
|
LaunchOrFocusPlugin(
|
||||||
|
RunPlugin {
|
||||||
|
_allow_exec_host_cmd: false,
|
||||||
|
location: Zellij(
|
||||||
|
PluginTag(
|
||||||
|
"session-manager",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
configuration: PluginUserConfiguration(
|
||||||
|
{
|
||||||
|
"floating": "true",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
SwitchToMode(
|
||||||
|
Normal,
|
||||||
|
),
|
||||||
|
],
|
||||||
Alt(
|
Alt(
|
||||||
Char(
|
Char(
|
||||||
'+',
|
'+',
|
||||||
|
|
@ -3586,6 +3609,23 @@ Config {
|
||||||
{},
|
{},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
PluginTag(
|
||||||
|
"session-manager",
|
||||||
|
): PluginConfig {
|
||||||
|
path: "session-manager",
|
||||||
|
run: Pane(
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
_allow_exec_host_cmd: false,
|
||||||
|
location: Zellij(
|
||||||
|
PluginTag(
|
||||||
|
"session-manager",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
userspace_configuration: PluginUserConfiguration(
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
},
|
||||||
PluginTag(
|
PluginTag(
|
||||||
"status-bar",
|
"status-bar",
|
||||||
): PluginConfig {
|
): PluginConfig {
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,23 @@ Config {
|
||||||
{},
|
{},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
PluginTag(
|
||||||
|
"session-manager",
|
||||||
|
): PluginConfig {
|
||||||
|
path: "session-manager",
|
||||||
|
run: Pane(
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
_allow_exec_host_cmd: false,
|
||||||
|
location: Zellij(
|
||||||
|
PluginTag(
|
||||||
|
"session-manager",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
userspace_configuration: PluginUserConfiguration(
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
},
|
||||||
PluginTag(
|
PluginTag(
|
||||||
"status-bar",
|
"status-bar",
|
||||||
): PluginConfig {
|
): PluginConfig {
|
||||||
|
|
|
||||||
|
|
@ -2489,6 +2489,29 @@ Config {
|
||||||
): [
|
): [
|
||||||
Detach,
|
Detach,
|
||||||
],
|
],
|
||||||
|
Char(
|
||||||
|
'w',
|
||||||
|
): [
|
||||||
|
LaunchOrFocusPlugin(
|
||||||
|
RunPlugin {
|
||||||
|
_allow_exec_host_cmd: false,
|
||||||
|
location: Zellij(
|
||||||
|
PluginTag(
|
||||||
|
"session-manager",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
configuration: PluginUserConfiguration(
|
||||||
|
{
|
||||||
|
"floating": "true",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
SwitchToMode(
|
||||||
|
Normal,
|
||||||
|
),
|
||||||
|
],
|
||||||
Alt(
|
Alt(
|
||||||
Char(
|
Char(
|
||||||
'+',
|
'+',
|
||||||
|
|
@ -3586,6 +3609,23 @@ Config {
|
||||||
{},
|
{},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
PluginTag(
|
||||||
|
"session-manager",
|
||||||
|
): PluginConfig {
|
||||||
|
path: "session-manager",
|
||||||
|
run: Pane(
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
_allow_exec_host_cmd: false,
|
||||||
|
location: Zellij(
|
||||||
|
PluginTag(
|
||||||
|
"session-manager",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
userspace_configuration: PluginUserConfiguration(
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
},
|
||||||
PluginTag(
|
PluginTag(
|
||||||
"some-other-plugin",
|
"some-other-plugin",
|
||||||
): PluginConfig {
|
): PluginConfig {
|
||||||
|
|
|
||||||
|
|
@ -2489,6 +2489,29 @@ Config {
|
||||||
): [
|
): [
|
||||||
Detach,
|
Detach,
|
||||||
],
|
],
|
||||||
|
Char(
|
||||||
|
'w',
|
||||||
|
): [
|
||||||
|
LaunchOrFocusPlugin(
|
||||||
|
RunPlugin {
|
||||||
|
_allow_exec_host_cmd: false,
|
||||||
|
location: Zellij(
|
||||||
|
PluginTag(
|
||||||
|
"session-manager",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
configuration: PluginUserConfiguration(
|
||||||
|
{
|
||||||
|
"floating": "true",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
SwitchToMode(
|
||||||
|
Normal,
|
||||||
|
),
|
||||||
|
],
|
||||||
Alt(
|
Alt(
|
||||||
Char(
|
Char(
|
||||||
'+',
|
'+',
|
||||||
|
|
@ -3890,6 +3913,23 @@ Config {
|
||||||
{},
|
{},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
PluginTag(
|
||||||
|
"session-manager",
|
||||||
|
): PluginConfig {
|
||||||
|
path: "session-manager",
|
||||||
|
run: Pane(
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
_allow_exec_host_cmd: false,
|
||||||
|
location: Zellij(
|
||||||
|
PluginTag(
|
||||||
|
"session-manager",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
userspace_configuration: PluginUserConfiguration(
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
},
|
||||||
PluginTag(
|
PluginTag(
|
||||||
"status-bar",
|
"status-bar",
|
||||||
): PluginConfig {
|
): PluginConfig {
|
||||||
|
|
|
||||||
|
|
@ -2489,6 +2489,29 @@ Config {
|
||||||
): [
|
): [
|
||||||
Detach,
|
Detach,
|
||||||
],
|
],
|
||||||
|
Char(
|
||||||
|
'w',
|
||||||
|
): [
|
||||||
|
LaunchOrFocusPlugin(
|
||||||
|
RunPlugin {
|
||||||
|
_allow_exec_host_cmd: false,
|
||||||
|
location: Zellij(
|
||||||
|
PluginTag(
|
||||||
|
"session-manager",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
configuration: PluginUserConfiguration(
|
||||||
|
{
|
||||||
|
"floating": "true",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
SwitchToMode(
|
||||||
|
Normal,
|
||||||
|
),
|
||||||
|
],
|
||||||
Alt(
|
Alt(
|
||||||
Char(
|
Char(
|
||||||
'+',
|
'+',
|
||||||
|
|
@ -3586,6 +3609,23 @@ Config {
|
||||||
{},
|
{},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
PluginTag(
|
||||||
|
"session-manager",
|
||||||
|
): PluginConfig {
|
||||||
|
path: "session-manager",
|
||||||
|
run: Pane(
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
_allow_exec_host_cmd: false,
|
||||||
|
location: Zellij(
|
||||||
|
PluginTag(
|
||||||
|
"session-manager",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
userspace_configuration: PluginUserConfiguration(
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
},
|
||||||
PluginTag(
|
PluginTag(
|
||||||
"status-bar",
|
"status-bar",
|
||||||
): PluginConfig {
|
): PluginConfig {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue