summaryrefslogtreecommitdiffstats
path: root/zellij-server
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2023-08-24 13:36:24 +0200
committerGitHub <noreply@github.com>2023-08-24 13:36:24 +0200
commitbc628abc1266cdc0dbce4f19a89727527a3e39a8 (patch)
tree6a0fcac6ec35c71a237bca0128a57a5b40afb0e3 /zellij-server
parentbf3c072d6dd68da0abd838e95e6004091a4cd331 (diff)
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
Diffstat (limited to 'zellij-server')
-rw-r--r--zellij-server/src/background_jobs.rs106
-rw-r--r--zellij-server/src/lib.rs73
-rw-r--r--zellij-server/src/panes/grid.rs2
-rw-r--r--zellij-server/src/panes/tiled_panes/mod.rs6
-rw-r--r--zellij-server/src/plugins/unit/plugin_tests.rs67
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__go_to_tab.snap4
-rw-r--r--zellij-server/src/plugins/wasm_bridge.rs1
-rw-r--r--zellij-server/src/plugins/zellij_exports.rs39
-rw-r--r--zellij-server/src/route.rs16
-rw-r--r--zellij-server/src/screen.rs279
-rw-r--r--zellij-server/src/tab/mod.rs3
11 files changed, 469 insertions, 127 deletions
diff --git a/zellij-server/src/background_jobs.rs b/zellij-server/src/background_jobs.rs
index 8061b4a47..67bd62c16 100644
--- a/zellij-server/src/background_jobs.rs
+++ b/zellij-server/src/background_jobs.rs
@@ -1,10 +1,16 @@
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 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::{
atomic::{AtomicBool, Ordering},
- Arc,
+ Arc, Mutex,
};
use std::time::{Duration, Instant};
@@ -15,8 +21,10 @@ use crate::thread_bus::Bus;
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum BackgroundJob {
DisplayPaneError(Vec<PaneId>, String),
- AnimatePluginLoading(u32), // u32 - plugin_id
- StopPluginLoadingAnimation(u32), // u32 - plugin_id
+ AnimatePluginLoading(u32), // u32 - plugin_id
+ StopPluginLoadingAnimation(u32), // u32 - plugin_id
+ ReadAllSessionInfosOnMachine, // u32 - plugin_id
+ ReportSessionInfo(String, SessionInfo), // String - session name
Exit,
}
@@ -28,6 +36,10 @@ impl From<&BackgroundJob> for BackgroundJobContext {
BackgroundJob::StopPluginLoadingAnimation(..) => {
BackgroundJobContext::StopPluginLoadingAnimation
},
+ BackgroundJob::ReadAllSessionInfosOnMachine => {
+ BackgroundJobContext::ReadAllSessionInfosOnMachine
+ },
+ BackgroundJob::ReportSessionInfo(..) => BackgroundJobContext::ReportSessionInfo,
BackgroundJob::Exit => BackgroundJobContext::Exit,
}
}
@@ -35,11 +47,14 @@ impl From<&BackgroundJob> for BackgroundJobContext {
static FLASH_DURATION_MS: u64 = 1000;
static PLUGIN_ANIMATION_OFFSET_DURATION_MD: u64 = 500;
+static SESSION_READ_DURATION: u64 = 1000;
pub(crate) fn background_jobs_main(bus: Bus<BackgroundJob>) -> Result<()> {
let err_context = || "failed to write to pty".to_string();
let mut running_jobs: HashMap<BackgroundJob, Instant> = HashMap::new();
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 {
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);
}
},
+ 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(&current_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,
+ &current_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 => {
for loading_plugin in loading_plugins.values() {
loading_plugin.store(false, Ordering::SeqCst);
}
+
+ let cache_file_name =
+ session_info_cache_file_name(&current_session_name.lock().unwrap().to_owned());
+ let _ = std::fs::remove_file(cache_file_name);
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))
+}
diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs
index 53a08f656..0b5f2ea8d 100644
--- a/zellij-server/src/lib.rs
+++ b/zellij-server/src/lib.rs
@@ -41,7 +41,7 @@ use zellij_utils::{
channels::{self, ChannelWithContext, SenderWithContext},
cli::CliArgs,
consts::{DEFAULT_SCROLL_BUFFER_SIZE, SCROLL_BUFFER_SIZE},
- data::{Event, PluginCapabilities},
+ data::{ConnectToSession, Event, PluginCapabilities},
errors::{prelude::*, ContextType, ErrorInstruction, FatalError, ServerContext},
input::{
command::{RunCommand, TerminalAction},
@@ -74,10 +74,17 @@ pub enum ServerInstruction {
Error(String),
KillSession,
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),
ActiveClients(ClientId),
Log(Vec<String>, ClientId),
+ SwitchSession(ConnectToSession, ClientId),
}
impl From<&ServerInstruction> for ServerContext {
@@ -95,6 +102,7 @@ impl From<&ServerInstruction> for ServerContext {
ServerInstruction::ConnStatus(..) => ServerContext::ConnStatus,
ServerInstruction::ActiveClients(_) => ServerContext::ActiveClients,
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))
.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 session_data = rlock.as_ref().unwrap();
session_state
@@ -433,7 +447,11 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.unwrap();
session_data
.senders
- .send_to_screen(ScreenInstruction::AddClient(client_id))
+ .send_to_screen(ScreenInstruction::AddClient(
+ client_id,
+ tab_position_to_focus,
+ pane_id_to_focus,
+ ))
.unwrap();
session_data
.senders
@@ -635,6 +653,41 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
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,
} = options;
- SCROLL_BUFFER_SIZE
- .set(
- config_options
- .scroll_buffer_size
- .unwrap_or(DEFAULT_SCROLL_BUFFER_SIZE),
- )
- .unwrap();
+ let _ = SCROLL_BUFFER_SIZE.set(
+ config_options
+ .scroll_buffer_size
+ .unwrap_or(DEFAULT_SCROLL_BUFFER_SIZE),
+ );
let (to_screen, screen_receiver): ChannelWithContext<ScreenInstruction> = channels::unbounded();
let to_screen = SenderWithContext::new(to_screen);
diff --git a/zellij-server/src/panes/grid.rs b/zellij-server/src/panes/grid.rs
index d63fac562..6cafad7d3 100644
--- a/zellij-server/src/panes/grid.rs
+++ b/zellij-server/src/panes/grid.rs
@@ -1351,7 +1351,7 @@ impl Grid {
self.viewport.get(y).unwrap().absolute_character_index(x)
}
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;
}
pub fn replace_characters_in_line_after_cursor(&mut self, replace_with: TerminalCharacter) {
diff --git a/zellij-server/src/panes/tiled_panes/mod.rs b/zellij-server/src/panes/tiled_panes/mod.rs
index 736c16bc9..e85911b4f 100644
--- a/zellij-server/src/panes/tiled_panes/mod.rs
+++ b/zellij-server/src/panes/tiled_panes/mod.rs
@@ -523,6 +523,11 @@ impl TiledPanes {
}
}
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
.panes
.get(&pane_id)
@@ -533,7 +538,6 @@ impl TiledPanes {
.expand_pane(&pane_id);
self.reapply_pane_frames();
}
-
self.active_panes
.insert(client_id, pane_id, &mut self.panes);
if self.session_is_mirrored {
diff --git a/zellij-server/src/plugins/unit/plugin_tests.rs b/zellij-server/src/plugins/unit/plugin_tests.rs
index e995a07b9..222b14c65 100644
--- a/zellij-server/src/plugins/unit/plugin_tests.rs
+++ b/zellij-server/src/plugins/unit/plugin_tests.rs
@@ -539,6 +539,7 @@ pub fn load_new_plugin_from_hd() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
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
// 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
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
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
// 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
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -820,6 +823,7 @@ pub fn switch_to_mode_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -887,6 +891,7 @@ pub fn switch_to_mode_plugin_command_permission_denied() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -954,6 +959,7 @@ pub fn new_tabs_with_layout_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1035,6 +1041,7 @@ pub fn new_tab_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1102,6 +1109,7 @@ pub fn go_to_next_tab_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1168,6 +1176,7 @@ pub fn go_to_previous_tab_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1234,6 +1243,7 @@ pub fn resize_focused_pane_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1300,6 +1310,7 @@ pub fn resize_focused_pane_with_direction_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1366,6 +1377,7 @@ pub fn focus_next_pane_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1432,6 +1444,7 @@ pub fn focus_previous_pane_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1498,6 +1511,7 @@ pub fn move_focus_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1564,6 +1578,7 @@ pub fn move_focus_or_tab_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1630,6 +1645,7 @@ pub fn edit_scrollback_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1696,6 +1712,7 @@ pub fn write_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1762,6 +1779,7 @@ pub fn write_chars_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1828,6 +1846,7 @@ pub fn toggle_tab_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1894,6 +1913,7 @@ pub fn move_pane_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1960,6 +1980,7 @@ pub fn move_pane_with_direction_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -2027,7 +2048,7 @@ pub fn clear_screen_plugin_command() {
client_id,
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![(
None,
Some(client_id),
@@ -2095,6 +2116,7 @@ pub fn scroll_up_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -2161,6 +2183,7 @@ pub fn scroll_down_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -2227,6 +2250,7 @@ pub fn scroll_to_top_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -2293,6 +2317,7 @@ pub fn scroll_to_bottom_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -2359,6 +2384,7 @@ pub fn page_scroll_up_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -2425,6 +2451,7 @@ pub fn page_scroll_down_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -2491,6 +2518,7 @@ pub fn toggle_focus_fullscreen_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -2557,6 +2585,7 @@ pub fn toggle_pane_frames_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -2623,6 +2652,7 @@ pub fn toggle_pane_embed_or_eject_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -2689,6 +2719,7 @@ pub fn undo_rename_pane_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -2755,6 +2786,7 @@ pub fn clo