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 /default-plugins/session-manager/src/main.rs | |
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
Diffstat (limited to 'default-plugins/session-manager/src/main.rs')
-rw-r--r-- | default-plugins/session-manager/src/main.rs | 473 |
1 files changed, 314 insertions, 159 deletions
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_resurrect_session_key(&mut self, key: Key) -> bool { + let mut should_render = false; + if let Key::Down = key { + self.resurrectable_sessions.move_selection_down(); + should_render = true; + } else if let Key::Up = key { + self.resurrectable_sessions.move_selection_up(); + should_render = true; + } else if let Key::Char(character) = key { + if character == '\n' { + self.handle_selection(); } else { - switch_session(Some(new_session_name)); + self.resurrectable_sessions.handle_character(character); } - } else if let Some(renaming_session_name) = &self.renaming_session_name.take() { - if renaming_session_name.is_empty() { - // TODO: implement these, then implement the error UI, then implement the renaming - // session screen, then test it - self.show_error("New name must not be empty."); - return; // s that we don't hide self - } else if self.session_name.as_ref() == Some(renaming_session_name) { - // noop - we're already called that! - return; // s that we don't hide self - } else if self.sessions.has_session(&renaming_session_name) { - self.show_error("A session by this name already exists."); - return; // s that we don't hide self - } else if self - .resurrectable_sessions - .has_session(&renaming_session_name) - { - self.show_error("A resurrectable session by this name already exists."); - return; // s that we don't hide self - } else { - self.update_current_session_name_in_ui(&renaming_session_name); - rename_session(&renaming_session_name); - return; // s that we don't hide self + should_render = true; + } else if let Key::Backspace = key { + self.resurrectable_sessions.handle_backspace(); + should_render = true; + } else if let Key::Ctrl('w') = key { + self.active_screen = ActiveScreen::NewSession; + should_render = true; + } else if let Key::BackTab = key { + self.toggle_active_screen(); + should_render = true; + } else if let Key::Delete = key { + self.resurrectable_sessions.delete_selected_session(); + should_render = true; + } else if let Key::Ctrl('d') = key { + self.resurrectable_sessions + .show_delete_all_sessions_warning(); + should_render = true; + } else if let Key::Esc = key { + if !self.is_welcome_screen { + hide_self(); } - } 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); + } + should_render + } + fn handle_selection(&mut self) { + match self.active_screen { + ActiveScreen::NewSession => { + self.new_session_info.handle_selection(&self.session_name); + }, + ActiveScreen::AttachToSession => { + if let Some(renaming_session_name) = &self.renaming_session_name.take() { + if renaming_session_name.is_empty() { + self.show_error("New name must not be empty."); + return; // so that we don't hide self + } else if self.session_name.as_ref() == Some(renaming_session_name) { + // noop - we're already called that! + return; // so that we don't hide self + } else if self.sessions.has_session(&renaming_session_name) { + self.show_error("A session by this name already exists."); + return; // so that we don't hide self + } else if self + .resurrectable_sessions + .has_session(&renaming_session_name) + { + self.show_error("A resurrectable session by this name already exists."); + return; // s that we don't hide self } else { - focus_terminal_pane(pane_id, true); + self.update_current_session_name_in_ui(&renaming_session_name); + rename_session(&renaming_session_name); + return; // s that we don't hide self } - } 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); - } + 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, + ); + } + } + self.reset_selected_index(); + self.search_term.clear(); + self.sessions + .update_search_term(&self.search_term, &self.colors); + hide_self(); + }, + ActiveScreen::ResurrectSession => { + if let Some(session_name_to_resurrect) = + self.resurrectable_sessions.get_selected_session_name() + { + switch_session(Some(&session_name_to_resurrect)); + } + }, } - self.reset_selected_index(); - self.new_session_name = None; - self.search_term.clear(); - self.sessions - .update_search_term(&self.search_term, &self.colors); - hide_self(); + } + fn toggle_active_screen(&mut self) { + self.active_screen = match self.active_screen { + ActiveScreen::NewSession => ActiveScreen::AttachToSession, + ActiveScreen::AttachToSession => ActiveScreen::ResurrectSession, + ActiveScreen::ResurrectSession => ActiveScreen::NewSession, + }; } fn show_error(&mut self, error_text: &str) { self.error = Some(error_text.to_owned()); @@ -329,4 +435,53 @@ impl State { } self.sessions.set_sessions(session_infos); } + fn main_menu_size(&self, rows: usize, cols: usize) -> (usize, usize, usize, usize) { + // x, y, width, height + let width = if self.is_welcome_screen { + std::cmp::min(cols, 101) + } else { + cols + }; + let x = if self.is_welcome_screen { + (cols.saturating_sub(width) as f64 / 2.0).floor() as usize + 2 + } else { + 0 + }; + let y = if self.is_welcome_screen { + (rows.saturating_sub(15) as f64 / 2.0).floor() as usize + } else { + 0 + }; + let height = rows.saturating_sub(y); + (x, y, width, height) + } + fn render_kill_all_sessions_warning(&self, rows: usize, columns: usize, x: usize, y: usize) { + if rows == 0 || columns == 0 { + return; + } + let session_count = self.sessions.all_other_sessions().len(); + let session_count_len = session_count.to_string().chars().count(); + let warning_description_text = format!("This will kill {} active sessions", session_count); + let confirmation_text = "Are you sure? (y/n)"; + let warning_y_location = y + (rows / 2).saturating_sub(1); + let confirmation_y_location = y + (rows / 2) + 1; + let warning_x_location = + x + columns.saturating_sub(warning_description_text.chars().count()) / 2; + let confirmation_x_location = + x + columns.saturating_sub(confirmation_text.chars().count()) / 2; + print_text_with_coordinates( + Text::new(warning_description_text).color_range(0, 15..16 + session_count_len), + warning_x_location, + warning_y_location, + None, + None, + ); + print_text_with_coordinates( + Text::new(confirmation_text).color_indices(2, vec![15, 17]), + confirmation_x_location, + confirmation_y_location, + None, + None, + ); + } } |