diff options
author | Aram Drevekenin <aram@poor.dev> | 2023-08-24 13:36:24 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-08-24 13:36:24 +0200 |
commit | bc628abc1266cdc0dbce4f19a89727527a3e39a8 (patch) | |
tree | 6a0fcac6ec35c71a237bca0128a57a5b40afb0e3 /zellij-server | |
parent | bf3c072d6dd68da0abd838e95e6004091a4cd331 (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.rs | 106 | ||||
-rw-r--r-- | zellij-server/src/lib.rs | 73 | ||||
-rw-r--r-- | zellij-server/src/panes/grid.rs | 2 | ||||
-rw-r--r-- | zellij-server/src/panes/tiled_panes/mod.rs | 6 | ||||
-rw-r--r-- | zellij-server/src/plugins/unit/plugin_tests.rs | 67 | ||||
-rw-r--r-- | zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__go_to_tab.snap | 4 | ||||
-rw-r--r-- | zellij-server/src/plugins/wasm_bridge.rs | 1 | ||||
-rw-r--r-- | zellij-server/src/plugins/zellij_exports.rs | 39 | ||||
-rw-r--r-- | zellij-server/src/route.rs | 16 | ||||
-rw-r--r-- | zellij-server/src/screen.rs | 279 | ||||
-rw-r--r-- | zellij-server/src/tab/mod.rs | 3 |
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(¤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 => { for loading_plugin in loading_plugins.values() { 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(()); }, } @@ -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 |