diff options
author | Aram Drevekenin <aram@poor.dev> | 2024-02-06 14:26:14 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-06 14:26:14 +0100 |
commit | 6b20a958f4e8db5614fffb27fe5d32f07ddfe855 (patch) | |
tree | c8383fb7941b5e0c23cb348a655f2ea0fafd8668 | |
parent | 286f7ccc28e3a091e2d3ef5663ae7878056551e6 (diff) |
feat(sessions): welcome screen (#3112)
* prototype - can send layout name for new session from session-manager
* feat(sessions): ui for selecting layout for new session in the session-manager
* fix: send available layouts to plugins
* make tests compile
* fix tests
* improve ui
* fix: respect built-in layouts
* ui for built-in layouts
* some cleanups
* style(fmt): rustfmt
* welcome screen ui
* fix: make sure layout config is not shared between sessions
* allow disconnecting other users from current session and killing other sessions
* fix: respect default layout
* add welcome screen layout
* tests(plugins): new api methods
* fix(session-manager): do not quit welcome screen on esc and break
* fix(plugins): adjust permissions
* style(fmt): rustfmt
* style(fmt): fix warnings
36 files changed, 1935 insertions, 394 deletions
diff --git a/default-plugins/fixture-plugin-for-tests/src/main.rs b/default-plugins/fixture-plugin-for-tests/src/main.rs index fcdc363f7..17f299435 100644 --- a/default-plugins/fixture-plugin-for-tests/src/main.rs +++ b/default-plugins/fixture-plugin-for-tests/src/main.rs @@ -280,6 +280,16 @@ impl ZellijPlugin for State { context, ); }, + Key::Ctrl('5') => { + switch_session(Some("my_new_session")); + }, + Key::Ctrl('6') => disconnect_other_clients(), + Key::Ctrl('7') => { + switch_session_with_layout( + Some("my_other_new_session"), + LayoutInfo::BuiltIn("compact".to_owned()), + ); + }, _ => {}, }, Event::CustomMessage(message, payload) => { diff --git a/default-plugins/session-manager/src/main.rs b/default-plugins/session-manager/src/main.rs index 8f70cc295..8040f79e0 100644 --- a/default-plugins/session-manager/src/main.rs +++ b/default-plugins/session-manager/src/main.rs @@ -1,3 +1,4 @@ +mod new_session_info; mod resurrectable_sessions; mod session_list; mod ui; @@ -5,34 +6,58 @@ use zellij_tile::prelude::*; use std::collections::BTreeMap; +use new_session_info::NewSessionInfo; use ui::{ components::{ - render_controls_line, render_error, render_new_session_line, render_prompt, - render_renaming_session_screen, render_resurrection_toggle, Colors, + render_controls_line, render_error, render_new_session_block, render_prompt, + render_renaming_session_screen, render_screen_toggle, Colors, }, + welcome_screen::{render_banner, render_welcome_boundaries}, SessionUiInfo, }; use resurrectable_sessions::ResurrectableSessions; use session_list::SessionList; +#[derive(Clone, Debug, Copy)] +enum ActiveScreen { + NewSession, + AttachToSession, + ResurrectSession, +} + +impl Default for ActiveScreen { + fn default() -> Self { + ActiveScreen::AttachToSession + } +} + #[derive(Default)] struct State { session_name: Option<String>, sessions: SessionList, resurrectable_sessions: ResurrectableSessions, search_term: String, - new_session_name: Option<String>, + new_session_info: NewSessionInfo, renaming_session_name: Option<String>, error: Option<String>, - browsing_resurrection_sessions: bool, + active_screen: ActiveScreen, colors: Colors, + is_welcome_screen: bool, + show_kill_all_sessions_warning: bool, } register_plugin!(State); impl ZellijPlugin for State { - fn load(&mut self, _configuration: BTreeMap<String, String>) { + fn load(&mut self, configuration: BTreeMap<String, String>) { + self.is_welcome_screen = configuration + .get("welcome_screen") + .map(|v| v == "true") + .unwrap_or(false); + if self.is_welcome_screen { + self.active_screen = ActiveScreen::NewSession; + } subscribe(&[ EventType::ModeUpdate, EventType::SessionUpdate, @@ -55,6 +80,12 @@ impl ZellijPlugin for State { should_render = true; }, Event::SessionUpdate(session_infos, resurrectable_session_list) => { + for session_info in &session_infos { + if session_info.is_current_session { + self.new_session_info + .update_layout_list(session_info.available_layouts.clone()); + } + } self.resurrectable_sessions .update(resurrectable_session_list); self.update_session_infos(session_infos); @@ -66,36 +97,53 @@ impl ZellijPlugin for State { } fn render(&mut self, rows: usize, cols: usize) { - if self.browsing_resurrection_sessions { - self.resurrectable_sessions.render(rows, cols); - return; - } else if let Some(new_session_name) = self.renaming_session_name.as_ref() { - render_renaming_session_screen(&new_session_name, rows, cols); - return; + let (x, y, width, height) = self.main_menu_size(rows, cols); + + if self.is_welcome_screen { + render_banner(x, 0, rows.saturating_sub(height), width); } - render_resurrection_toggle(cols, false); - 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_screen_toggle(self.active_screen, x, y, width.saturating_sub(2)); + + match self.active_screen { + ActiveScreen::NewSession => { + render_new_session_block( + &self.new_session_info, + self.colors, + height, + width, + x, + y + 2, + ); + }, + ActiveScreen::AttachToSession => { + if let Some(new_session_name) = self.renaming_session_name.as_ref() { + render_renaming_session_screen(&new_session_name, height, width, x, y + 2); + } else if self.show_kill_all_sessions_warning { + self.render_kill_all_sessions_warning(height, width, x, y); + } else { + render_prompt(&self.search_term, self.colors, x, y + 2); + let room_for_list = height.saturating_sub(6); // search line and controls; + self.sessions.update_rows(room_for_list); + let list = + self.sessions + .render(room_for_list, width.saturating_sub(7), self.colors); // 7 for various ui + for (i, line) in list.iter().enumerate() { + print!("\u{1b}[{};{}H{}", y + i + 5, x, line.render()); + } + } + }, + ActiveScreen::ResurrectSession => { + self.resurrectable_sessions.render(height, width, x, y); + }, } - render_new_session_line( - &self.new_session_name, - self.sessions.is_searching, - self.colors, - ); if let Some(error) = self.error.as_ref() { - render_error(&error, rows, cols); + render_error(&error, height, width, x, y); } else { - render_controls_line(self.sessions.is_searching, rows, cols, self.colors); + render_controls_line(self.active_screen, width, self.colors, x + 1, rows); + } + if self.is_welcome_screen { + render_welcome_boundaries(rows, cols); // explicitly done in the end to override some + // stuff, see comment in function } } } @@ -109,40 +157,77 @@ impl State { self.error = None; return true; } + match self.active_screen { + ActiveScreen::NewSession => self.handle_new_session_key(key), + ActiveScreen::AttachToSession => self.handle_attach_to_session(key), + ActiveScreen::ResurrectSession => self.handle_resurrect_session_key(key), + } + } + fn handle_new_session_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(); + if let Key::Down = key { + self.new_session_info.handle_key(key); + should_render = true; + } else if let Key::Up = key { + self.new_session_info.handle_key(key); + should_render = true; + } else if let Key::Char(character) = key { + if character == '\n' { + self.handle_selection(); + } else { + self.new_session_info.handle_key(key); } should_render = true; - } else if let Key::Left = key { - if self.new_session_name.is_none() { - self.sessions.result_shrink(); + } else if let Key::Backspace = key { + self.new_session_info.handle_key(key); + should_render = true; + } else if let Key::Ctrl('w') = key { + self.active_screen = ActiveScreen::NewSession; + should_render = true; + } else if let Key::Ctrl('c') = key { + self.new_session_info.handle_key(key); + should_render = true; + } else if let Key::BackTab = key { + self.toggle_active_screen(); + should_render = true; + } else if let Key::Esc = key { + self.new_session_info.handle_key(key); + should_render = true; + } + should_render + } + fn handle_attach_to_session(&mut self, key: Key) -> bool { + let mut should_render = false; + if self.show_kill_all_sessions_warning { + if let Key::Char('y') = key { + let all_other_sessions = self.sessions.all_other_sessions(); + kill_sessions(&all_other_sessions); + self.reset_selected_index(); + self.search_term.clear(); + self.sessions + .update_search_term(&self.search_term, &self.colors); + self.show_kill_all_sessions_warning = false + } else if let Key::Char('n') | Key::Esc | Key::Ctrl('c') = key { + self.show_kill_all_sessions_warning = false } should_render = true; + } else if let Key::Right = key { + self.sessions.result_expand(); + should_render = true; + } else if let Key::Left = key { + self.sessions.result_shrink(); + should_render = true; } else if let Key::Down = key { - if self.browsing_resurrection_sessions { - self.resurrectable_sessions.move_selection_down(); - } else if self.new_session_name.is_none() && self.renaming_session_name.is_none() { - self.sessions.move_selection_down(); - } + self.sessions.move_selection_down(); should_render = true; } else if let Key::Up = key { - if self.browsing_resurrection_sessions { - self.resurrectable_sessions.move_selection_up(); - } else if self.new_session_name.is_none() && self.renaming_session_name.is_none() { - self.sessions.move_selection_up(); - } + 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() { + } else if let Some(new_session_name) = self.renaming_session_name.as_mut() { new_session_name.push(character); - } else if let Some(renaming_session_name) = self.renaming_session_name.as_mut() { - renaming_session_name.push(character); - } else if self.browsing_resurrection_sessions { - self.resurrectable_sessions.handle_character(character); } else { self.search_term.push(character); self.sessions @@ -150,20 +235,12 @@ impl State { } should_render = true; } else if let Key::Backspace = key { - if let Some(new_session_name) = self.new_session_name.as_mut() { + if let Some(new_session_name) = self.renaming_session_name.as_mut() { if new_session_name.is_empty() { - self.new_session_name = None; - } else { - new_session_name.pop(); - } - } else if let Some(renaming_session_name) = self.renaming_session_name.as_mut() { - if renaming_session_name.is_empty() { self.renaming_session_name = None; } else { - renaming_session_name.pop(); + new_session_name.pop(); } - } else if self.browsing_resurrection_sessions { - self.resurrectable_sessions.handle_backspace(); } else { self.search_term.pop(); self.sessions @@ -171,136 +248,165 @@ impl State { } should_render = true; } else if let Key::Ctrl('w') = key { - if self.sessions.is_searching || self.browsing_resurrection_sessions { - // no-op - } else if self.new_session_name.is_some() { - self.new_session_name = None; + self.active_screen = ActiveScreen::NewSession; + should_render = true; + } else if let Key::Ctrl('r') = key { + self.renaming_session_name = Some(String::new()); + should_render = true; + } else if let Key::Delete = key { + if let Some(selected_session_name) = self.sessions.get_selected_session_name() { + kill_sessions(&[selected_session_name]); + self.reset_selected_index(); + self.search_term.clear(); + self.sessions + .update_search_term(&self.search_term, &self.colors); } else { - self.new_session_name = Some(String::new()); + self.show_error("Must select session before killing it."); } should_render = true; - } else if let Key::Ctrl('r') = key { - if self.sessions.is_searching || self.browsing_resurrection_sessions { - // no-op - } else if self.renaming_session_name.is_some() { - self.renaming_session_name = None; + } else if let Key::Ctrl('d') = key { + let all_other_sessions = self.sessions.all_other_sessions(); + if all_other_sessions.is_empty() { + self.show_error("No other sessions to kill. Quit to kill the current one."); } else { - self.renaming_session_name = Some(String::new()); + self.show_kill_all_sessions_warning = true; } should_render = true; + } else if let Key::Ctrl('x') = key { + disconnect_other_clients(); } 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 let Some(renaming_session_name) = self.renaming_session_name.as_mut() { - if renaming_session_name.is_empty() { - self.renaming_session_name = None; - } else { - renaming_session_name.clear() - } - } else if !self.search_term.is_empty() { + 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 { + } else if !self.is_welcome_screen { self.reset_selected_index(); hide_self(); } should_render = true; } else if let Key::BackTab = key { - self.browsing_resurrection_sessions = !self.browsing_resurrection_sessions; + self.toggle_active_screen(); should_render = true; - } else if let Key::Delete = key { - if self.browsing_resurrection_sessions { - self.resurrectable_sessions.delete_selected_session(); - should_render = true; - } - } else if let Key::Ctrl('d') = key { - if self.browsing_resurrection_sessions { - self.resurrectable_sessions - .show_delete_all_sessions_warning(); - should_render = true; - } } else if let Key::Esc = key { if self.renaming_session_name.is_some() { self.renaming_session_name = None; should_render = true; - } else if self.new_session_name.is_some() { - self.new_session_name = None; - should_render = true; - } else { + } else if !self.is_welcome_screen { hide_self(); } } should_render } - fn handle_selection(&mut self) { - if self.browsing_resurrection_sessions { - if let Some(session_name_to_resurrect) = - self.resurrectable_sessions.get_selected_session_name() - { - switch_session(Some(&session_name_to_resurrect)); - } - } else 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; + fn handle |