aboutsummaryrefslogtreecommitdiffstats
path: root/default-plugins
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2024-02-06 14:26:14 +0100
committerGitHub <noreply@github.com>2024-02-06 14:26:14 +0100
commit6b20a958f4e8db5614fffb27fe5d32f07ddfe855 (patch)
treec8383fb7941b5e0c23cb348a655f2ea0fafd8668 /default-plugins
parentdocs(readme): add sponsor (diff)
downloadsl0ppij-6b20a958f4e8db5614fffb27fe5d32f07ddfe855.tar.gz
sl0ppij-6b20a958f4e8db5614fffb27fe5d32f07ddfe855.tar.xz
sl0ppij-6b20a958f4e8db5614fffb27fe5d32f07ddfe855.zip
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')
-rw-r--r--default-plugins/fixture-plugin-for-tests/src/main.rs10
-rw-r--r--default-plugins/session-manager/src/main.rs473
-rw-r--r--default-plugins/session-manager/src/new_session_info.rs265
-rw-r--r--default-plugins/session-manager/src/resurrectable_sessions.rs37
-rw-r--r--default-plugins/session-manager/src/session_list.rs12
-rw-r--r--default-plugins/session-manager/src/ui/components.rs382
-rw-r--r--default-plugins/session-manager/src/ui/mod.rs7
-rw-r--r--default-plugins/session-manager/src/ui/welcome_screen.rs168
8 files changed, 1054 insertions, 300 deletions
diff --git a/default-plugins/fixture-plugin-for-tests/src/main.rs b/default-plugins/fixture-plugin-for-tests/src/main.rs
index fcdc363f..17f29943 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 8f70cc29..8040f79e 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,
+ );
+ }
}
diff --git a/default-plugins/session-manager/src/new_session_info.rs b/default-plugins/session-manager/src/new_session_info.rs
new file mode 100644
index 00000000..6a2456f1
--- /dev/null
+++ b/default-plugins/session-manager/src/new_session_info.rs
@@ -0,0 +1,265 @@
+use fuzzy_matcher::skim::SkimMatcherV2;
+use fuzzy_matcher::FuzzyMatcher;
+use std::cmp::Ordering;
+use zellij_tile::prelude::*;
+
+#[derive(Default)]
+pub struct NewSessionInfo {
+ name: String,
+ layout_list: LayoutList,
+ entering_new_session_info: EnteringState,
+}
+
+#[derive(Eq, PartialEq)]
+enum EnteringState {
+ EnteringName,
+ EnteringLayoutSearch,
+}
+
+impl Default for EnteringState {
+ fn default() -> Self {
+ EnteringState::EnteringName
+ }
+}
+
+impl NewSessionInfo {
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+ pub fn layout_search_term(&self) -> &str {
+ &self.layout_list.layout_search_term
+ }
+ pub fn entering_new_session_info(&self) -> bool {
+ true
+ }
+ pub fn entering_new_session_name(&self) -> bool {
+ self.entering_new_session_info == EnteringState::EnteringName
+ }
+ pub fn entering_layout_search_term(&self) -> bool {
+ self.entering_new_session_info == EnteringState::EnteringLayoutSearch
+ }
+ pub fn add_char(&mut self, character: char) {
+ match self.entering_new_session_info {
+ EnteringState::EnteringName => {
+ self.name.push(character);
+ },
+ EnteringState::EnteringLayoutSearch => {
+ self.layout_list.layout_search_term.push(character);
+ self.update_layout_search_term();
+ },
+ }
+ }
+ pub fn handle_backspace(&mut self) {
+ match self.entering_new_session_info {
+ EnteringState::EnteringName => {
+ self.name.pop();
+ },
+ EnteringState::EnteringLayoutSearch => {
+ self.layout_list.layout_search_term.pop();
+ self.update_layout_search_term();
+ },
+ }
+ }
+ pub fn handle_break(&mut self) {
+ match self.entering_new_session_info {
+ EnteringState::EnteringName => {
+ self.name.clear();
+ },
+ EnteringState::EnteringLayoutSearch => {
+ self.layout_list.layout_search_term.clear();
+ self.entering_new_session_info = EnteringState::EnteringName;
+ self.update_layout_search_term();
+ },
+ }
+ }
+ pub fn handle_key(&mut self, key: Key) {
+ match key {
+ Key::Backspace => {
+ self.handle_backspace();
+ },
+ Key::Ctrl('c') | Key::Esc => {
+ self.handle_break();
+ },
+ Key::Char(character) => {
+ self.add_char(character);
+ },
+ Key::Up => {
+ self.move_selection_up();
+ },
+ Key::Down => {
+ self.move_selection_down();
+ },
+ _ => {},
+ }
+ }
+ pub fn handle_selection(&mut self, current_session_name: &Option<String>) {
+ match self.entering_new_session_info {
+ EnteringState::EnteringLayoutSearch => {
+ let new_session_layout: Option<LayoutInfo> = self.selected_layout_info();
+ let new_session_name = if self.name.is_empty() {
+ None
+ } else {
+ Some(self.name.as_str())
+ };
+ if new_session_name != current_session_name.as_ref().map(|s| s.as_str()) {
+ match new_session_layout {
+ Some(new_session_layout) => {
+ switch_session_with_layout(new_session_name, new_session_layout)
+ },
+ None => {
+ switch_session(new_session_name);
+ },
+ }
+ }
+ self.name.clear();
+ self.layout_list.clear_selection();
+ hide_self();
+ },
+ EnteringState::EnteringName => {
+ self.entering_new_session_info = EnteringState::EnteringLayoutSearch;
+ },
+ }
+ }
+ pub fn update_layout_list(&mut self, layout_info: Vec<LayoutInfo>) {
+ self.layout_list.update_layout_list(layout_info);
+ }
+ pub fn layout_list(&self) -> Vec<(LayoutInfo, bool)> {
+ // bool - is_selected
+ self.layout_list
+ .layout_list
+ .iter()
+ .enumerate()
+ .map(|(i, l)| (l.clone(), i == self.layout_list.selected_layout_index))
+ .collect()
+ }
+ pub fn layouts_to_render(&self) -> Vec<(LayoutInfo, Vec<usize>, bool)> {
+ // (layout_info,
+ // search_indices,
+ // is_selected)
+ if self.is_searching() {
+ self.layout_search_results()
+ .into_iter()
+ .map(|(layout_search_result, is_selected)| {
+ (
+ layout_search_result.layout_info,
+ layout_search_result.indices,
+ is_selected,
+ )
+ })
+ .collect()
+ } else {
+ self.layout_list()
+ .into_iter()
+ .map(|(layout_info, is_selected)| (layout_info, vec![], is_selected))
+ .collect()
+ }
+ }
+ pub fn layout_search_results(&self) -> Vec<(LayoutSearchResult, bool)> {
+ // bool - is_selected
+ self.layout_list
+ .layout_search_results
+ .iter()
+ .enumerate()
+ .map(|(i, l)| (l.clone(), i == self.layout_list.selected_layout_index))
+ .collect()
+ }
+ pub fn is_searching(&self) -> bool {
+ !self.layout_list.layout_search_term.is_empty()
+ }
+ pub fn layout_count(&self) -> usize {
+ self.layout_list.layout_list.len()
+ }
+ pub fn selected_layout_info(&self) -> Option<LayoutInfo> {
+ self.layout_list.selected_layout_info()
+ }
+ fn update_layout_search_term(&mut self) {
+ if self.layout_list.layout_search_term.is_empty() {
+ self.layout_list.clear_selection();
+ self.layout_list.layout_search_results = vec![];
+ } else {
+ let mut matches = vec![];
+ let matcher = SkimMatcherV2::default().use_cache(true);
+ for layout_info in &self.layout_list.layout_list {
+ if let Some((score, indices)) =
+ matcher.fuzzy_indices(&layout_info.name(), &self.layout_list.layout_search_term)
+ {
+ matches.push(LayoutSearchResult {
+ layout_info: layout_info.clone(),
+ score,
+ indices,
+ });
+ }
+ }
+ matches.sort_by(|a, b| b.score.cmp(&a.score));
+ self.layout_list.layout_search_results = matches;
+ self.layout_list.clear_selection();
+ }
+ }
+ fn move_selection_up(&mut self) {
+ self.layout_list.move_selection_up();
+ }
+ fn move_selection_down(&mut self) {
+ self.layout_list.move_selection_down();
+ }
+}
+
+#[derive(Default)]
+pub struct LayoutList {
+ layout_list: Vec<LayoutInfo>,
+ layout_search_results: Vec<LayoutSearchResult>,
+ selected_layout_index: usize,
+ layout_search_term: String,
+}
+
+impl LayoutList {
+ pub fn update_layout_list(&mut self, layout_list: Vec<LayoutInfo>) {
+ let old_layout_length = self.layout_list.len();
+ self.layout_list = layout_list;
+ if old_layout_length != self.layout_list.len() {
+ // honestly, this is just the UX choice that sucks the least...
+ self.clear_selection();
+ }
+ }
+ pub fn selected_layout_info(&self) -> Option<LayoutInfo> {
+ if !self.layout_search_term.is_empty() {
+ self.layout_search_results
+ .get(self.selected_layout_index)
+ .map(|l| l.layout_info.clone())
+ } else {
+ self.layout_list.get(self.selected_layout_index).cloned()
+ }
+ }
+ pub fn clear_selection(&mut self) {
+ self.selected_layout_index = 0;
+ }
+ fn max_index(&self) -> usize {
+ if self.layout_search_term.is_empty() {
+ self.layout_list.len().saturating_sub(1)
+ } else {
+ self.layout_search_results.len().saturating_sub(1)
+ }
+ }
+ fn move_selection_up(&mut self) {
+ let max_index = self.max_index();
+ if self.selected_layout_index > 0 {
+ self.selected_layout_index -= 1;
+ } else {
+ self.selected_layout_index = max_index;
+ }
+ }
+ fn move_selection_down(&mut self) {
+ let max_index = self.max_index();
+ if self.selected_layout_index < max_index {
+ self.selected_layout_index += 1;
+ } else {
+ self.selected_layout_index = 0;
+ }
+ }
+}
+
+#[derive(Clone)]
+pub struct LayoutSearchResult {
+ pub layout_info: LayoutInfo,
+ pub score: i64,
+ pub indices: Vec<usize>,
+}
diff --git a/default-plugins/session-manager/src/resurrectable_sessions.rs b/default-plugins/session-manager/src/resurrectable_sessions.rs
index dfbb86f0..98887036 100644
--- a/default-plugins/session-manager/src/resurrectable_sessions.rs
+++ b/default-plugins/session-manager/src/resurrectable_sessions.rs
@@ -2,8 +2,6 @@ use fuzzy_matcher::skim::SkimMatcherV2;
use fuzzy_matcher::FuzzyMatcher;
use humantime::format_duration;
-use crate::ui::components::render_resurrection_toggle;
-
use std::time::Duration;
use zellij_tile::shim::*;
@@ -24,23 +22,22 @@ impl ResurrectableSessions {
list.sort_by(|a, b| a.1.cmp(&b.1));
self.all_resurrectable_sessions = list;
}
- pub fn render(&self, rows: usize, columns: usize) {
+ pub fn render(&self, rows: usize, columns: usize, x: usize, y: usize) {
if self.delete_all_dead_sessions_warning {
- self.render_delete_all_sessions_warning(rows, columns);
+ self.render_delete_all_sessions_warning(rows, columns, x, y);
return;
}
- render_resurrection_toggle(columns, true);
- let search_indication = Text::new(format!("> {}_", self.search_term)).color_range(1, ..);
- let table_rows = rows.saturating_sub(3);
+ let search_indication =
+ Text::new(format!("Search: {}_", self.search_term)).color_range(2, ..7);
+ let table_rows = rows.saturating_sub(5); // search row, toggle row and some padding
let table_columns = columns;
let table = if self.is_searching {
self.render_search_results(table_rows, columns)
} else {
self.render_all_entries(table_rows, columns)
};
- print_text_with_coordinates(search_indication, 0, 0, None, None);
- print_table_with_coordinates(table, 0, 1, Some(table_columns), Some(table_rows));
- self.render_controls_line(rows);
+ print_text_with_coordinates(search_indication, x.saturating_sub(1), y + 2, None, None);
+ print_table_with_coordinates(table, x, y + 3, Some(table_columns), Some(table_rows));
}
fn render_search_results(&self, table_rows: usize, _table_columns: usize) -> Table {
let mut table = Table::new().add_row(vec![" ", " ", " "]); // skip the title row
@@ -103,7 +100,7 @@ impl ResurrectableSessions {
}
table
}
- fn render_delete_all_sessions_warning(&self, rows: usize, columns: usize) {
+ fn render_delete_all_sessions_warning(&self, rows: usize, columns: usize, x: usize, y: usize) {
if rows == 0 || columns == 0 {
return;
}
@@ -112,11 +109,12 @@ impl ResurrectableSessions {
let warning_description_text =
format!("This will delete {} resurrectable sessions", session_count,);
let confirmation_text = "Are you sure? (y/n)";
- let warning_y_location = (rows / 2).saturating_sub(1);
- let confirmation_y_location = (rows / 2) + 1;
+ let warning_y_location = y + (rows / 2).saturating_sub(1);
+ let confirmation_y_location = y + (rows / 2) + 1;
let warning_x_location =
- columns.saturating_sub(warning_description_text.chars().count()) / 2;
- let confirmation_x_location = columns.saturating_sub(confirmation_text.chars().count()) / 2;
+ 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, 17..18 + session_count_len),
warning_x_location,
@@ -200,15 +198,6 @@ impl ResurrectableSessions {
Text::new(" ")
}
}
- fn render_controls_line(&self, rows: usize) {
- let controls_line = Text::new(format!(
- "Help: <↓↑> - Navigate, <DEL> - Delete Session, <Ctrl d> - Delete all sessions"
- ))
- .color_range(3, 6..10)
- .color_range(3, 23..29)
- .color_range(3, 47..56);
- print_text_with_coordinates(controls_line, 0, rows.saturating_sub(1), None, None);
- }
pub fn move_selection_down(&mut self) {
if self.is_searching {
if let Some(selected_index) = self.selected_search_index.as_mut() {
diff --git a/default-plugins/session-manager/src/session_list.rs b/default-plugins/session-manager/src/session_list.rs
index fa63571e..01c75ad5 100644
--- a/default-plugins/session-manager/src/session_list.rs
+++ b/default-plugins/session-manager/src/session_list.rs
@@ -323,6 +323,18 @@ impl SessionList {
.find(|s| s.name == old_name)
.map(|s| s.name = new_name.to_owned());
}
+ pub fn all_other_sessions(&self) -> Vec<String> {
+ self.session_ui_infos
+ .iter()
+ .filter_map(|s| {
+ if !s.is_current_session {
+ Some(s.name.clone())
+ } else {
+ None
+ }
+ })
+ .collect()
+ }
}
#[derive(Debug, Clone, Default)]
diff --git a/default-plugins/session-manager/src/ui/components.rs b/default-plugins/session-manager/src/ui/components.rs
index 4d9839c0..d3c4afe8 100644
--- a/default-plugins/session-manager/src/ui/components.rs
+++ b/default-plugins/session-manager/src/ui/components.rs
@@ -3,6 +3,7 @@ use unicode_width::UnicodeWidthStr;
use zellij_tile::prelude::*;
use crate::ui::{PaneUiInfo, SessionUiInfo, TabUiInfo};
+use crate::{ActiveScreen, NewSessionInfo};
#[derive(Debug)]
pub struct ListItem {
@@ -292,18 +293,45 @@ impl LineToRender {
pub fn append(&mut self, to_append: &str) {
self.line.push_str(to_append)
}
- pub fn make_selected(&mut self) {
+ pub fn make_selected_as_search(&mut self, add_arrows: bool) {
self.is_selected = true;
+ let arrows = if add_arrows {
+ self.colors.magenta(" <↓↑> ")
+ } else {
+ " ".to_owned()
+ };
+ match self.colors.palette.bg {
+ PaletteColor::EightBit(byte) => {
+ self.line = format!(
+ "\u{1b}[48;5;{byte}m\u{1b}[K\u{1b}[48;5;{byte}m{arrows}{}",
+ self.line
+ );
+ },
+ PaletteColor::Rgb((r, g, b)) => {
+ self.line = format!(
+ "\u{1b}[48;2;{};{};{}m\u{1b}[K\u{1b}[48;2;{};{};{}m{arrows}{}",
+ r, g, b, r, g, b, self.line
+ );
+ },
+ }
+ }
+ pub fn make_selected(&mut self, add_arrows: bool) {
+ self.is_selected = true;
+ let arrows = if add_arrows {
+ self.colors.magenta("<←↓↑→>")
+ } else {
+ " ".to_owned()
+ };
match self.colors.palette.bg {
PaletteColor::EightBit(byte) => {
self.line = format!(
- "\u{1b}[48;5;{byte}m\u{1b}[K\r\u{1b}[48;5;{byte}m{}",
+ "\u{1b}[48;5;{byte}m\u{1b}[K\u{1b}[48;5;{byte}m{arrows}{}",
self.line
);
},
PaletteColor::Rgb((r, g, b)) => {
self.line = format!(
- "\u{1b}[48;2;{};{};{}m\u{1b}[K\r\u{1b}[48;2;{};{};{}m{}",
+ "\u{1b}[48;2;{};{};{}m\u{1b}[K\u{1b}[48;2;{};{};{}m{arrows}{}",
r, g, b, r, g, b, self.line
);
},
@@ -323,7 +351,7 @@ impl LineToRender {
if self.is_selected {
self.line.clone()
} else {
- format!("\u{1b}[49m{}", line)
+ format!("\u{1b}[49m {}", line)
}
}
pub fn add_truncated_results(&mut self, result_count: usize) {
@@ -475,151 +503,275 @@ pub fn minimize_lines(
(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!("\u{1b}[H{}\n", prompt);
- } else {
- println!("\n");
- }
+pub fn render_prompt(search_term: &str, colors: Colors, x: usize, y: usize) {
+ let prompt = colors.green(&format!("Search:"));
+ let search_term = colors.bold(&format!("{}_", search_term));
+ println!("\u{1b}[{};{}H{} {}\n", y + 1, x, prompt, search_term);
}
-pub fn render_resurrection_toggle(cols: usize, resurrection_screen_is_active: bool) {
+pub fn render_screen_toggle(active_screen: ActiveScreen, x: usize, y: usize, max_cols: usize) {
let key_indication_text = "<TAB>";
- let running_sessions_text = "Running";
- let exited_sessions_text = "Exited";
+ let (new_session_text, running_sessions_text, exited_sessions_text) = if max_cols > 66 {
+ ("New Session", "Attach to Session", "Resurrect Session")
+ } else {
+ ("New", "Attach", "Resurrect")
+ };
let key_indication_len = key_indication_text.chars().count() + 1;
- let first_ribbon_length = running_sessions_text.chars().count() + 4;
- let second_ribbon_length = exited_sessions_text.chars().count() + 4;
- let key_indication_x =
- cols.saturating_sub(key_indication_len + first_ribbon_length + second_ribbon_length);
+ let first_ribbon_length = new_session_text.chars().count() + 4;
+ let second_ribbon_length = running_sessions_text.chars().count() + 4;
+ let third_ribbon_length = exited_sessions_text.chars().count() + 4;
+ let total_len =
+ key_indication_len + first_ribbon_length + second_ribbon_length + third_ribbon_length;
+ let key_indication_x = x;
let first_ribbon_x = key_indication_x + key_indication_len;
let second_ribbon_x = first_ribbon_x + first_ribbon_length;
+ let third_ribbon_x = second_ribbon_x + second_ribbon_length;
+ let mut new_session_text = Text::new(new_session_text);
+ let mut running_sessions_text = Text::new(running_sessions_text);
+ let mut exited_sessions_text = Text::new(exited_sessions_text);
+ match active_screen {
+ ActiveScreen::NewSession => {
+ new_session_text = new_session_text.selected();
+ },
+ ActiveScreen::AttachToSession => {
+ running_sessions_text = running_sessions_text.selected();
+ },
+ ActiveScreen::ResurrectSession => {
+ exited_sessions_text = exited_sessions_text.selected();
+ },
+ }
print_text_with_coordinates(
Text::new(key_indication_text).color_range(3, ..),
key_indication_x,
- 0,
+ y,
None,
None,
);
- if resurrection_screen_is_active {
- print_ribbon_with_coordinates(
- Text::new(running_sessions_text),
- first_ribbon_x,
- 0,
- None,
- None,
- );
- print_ribbon_with_coordinates(
- Text::new(exited_sessions_text).selected(),
- second_ribbon_x,
- 0,
- None,
- None,
- );
- } else {
- print_ribbon_with_coordinates(
- Text::new(running_sessions_text).selected(),
- first_ribbon_x,
- 0,
- None,
- None,
- );
- print_ribbon_with_coordinates(
- Text::new(exited_sessions_text),
- second_ribbon_x,
- 0,
- None,
- None,
- );
- }
+ print_ribbon_with_coordinates(new_session_text, first_ribbon_x, y, None, None);
+ print_ribbon_with_coordinates(running_sessions_text, second_ribbon_x, y, None, None);
+ print_ribbon_with_coordinates(exited_sessions_text, third_ribbon_x, y, None, None);
}
-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");
+pub fn render_new_session_block(
+ new_session_info: &NewSessionInfo,
+ colors: Colors,
+ max_rows_of_new_session_block: usize,
+ max_cols_of_new_session_block: usize,
+ x: usize,
+ y: usize,
+) {
let enter = colors.magenta("<ENTER>");
- match session_name {
- Some(session_name) => {
+ if new_session_info.entering_new_session_name() {
+ let prompt = "New session name:";
+ let long_instruction = "when done, blank for random";
+ let new_session_name = new_session_info.name();
+ if max_cols_of_new_session_block
+ > prompt.width() + long_instruction.width() + new_session_name.width() + 15
+ {
println!(
- "\u{1b}[m > {}_ ({}, {} when done)",
- colors.orange(session_name),
- colors.bold("Type optional name"),
- enter
+ "\u{1b}[m{}{} {}_ ({} {})",
+ format!("\u{1b}[{};{}H", y + 1, x + 1),
+ colors.green(prompt),
+ colors.orange(&new_session_name),
+ enter,
+ long_instruction,
);
- },
- None => {
- println!("\u{1b}[m > {new_session_shortcut} - {new_session}");
- },
+ } else {
+ println!(
+ "\u{1b}[m{}{} {}_ {}",
+ format!("\u{1b}[{};{}H", y + 1, x + 1),
+ colors.green(prompt),
+ colors.orange(&new_session_name),
+ enter,
+ );
+ }
+ } else if new_session_info.entering_layout_search_term() {
+ let new_session_name = if new_session_info.name().is_empty() {
+ "<RANDOM>"
+ } else {
+ new_session_info.name()
+ };
+ let prompt = "New session name:";
+ let long_instruction = "to correct";
+ let esc = colors.magenta("<ESC>");
+ if max_cols_of_new_session_block
+ > prompt.width() + long_instruction.width() + new_session_name.width() + 15
+ {
+ println!(
+ "\u{1b}[m{}{}: {} ({} to correct)",
+ format!("\u{1b}[{};{}H", y + 1, x + 1),
+ colors.green("New session name"),
+ colors.orange(new_session_name),
+ esc,
+ );
+ } else {
+ println!(
+ "\u{1b}[m{}{}: {} {}",
+ format!("\u{1b}[{};{}H", y + 1, x + 1),
+ colors.green("New session name"),
+ colors.orange(new_session_name),
+ esc,
+ );
+ }
+ render_layout_selection_list(
+ new_session_info,
+ max_rows_of_new_session_block.saturating_sub(1),
+ max_cols_of_new_session_block,
+ x,
+ y + 1,
+ );
}
}
-pub fn render_error(error_text: &str, rows: usize, columns: usize) {
+pub fn render_layout_selection_list(
+ new_session_info: &NewSessionInfo,
+ max_rows_of_new_session_block: usize,
+ max_cols_of_new_session_block: usize,
+ x: usize,
+ y: usize,
+) {
+ let layout_search_term = new_session_info.layout_search_term();
+ let search_term_len = layout_search_term.width();
+ let layout_indication_line = if max_cols_of_new_session_block > 73 + search_term_len {
+ Text::new(format!(
+ "New session layout: {}_ (Search and select from list, <ENTER> when done)",
+ layout_search_term
+ ))
+ .color_range(2, ..20 + search_term_len)
+ .color_range(3, 20..20 + search_term_len)
+ .color_range(3, 52 + search_term_len..59 + search_term_len)
+ } else {
+ Text::new(format!(
+ "New session layout: {}_ <ENTER>",
+ layout_search_term
+ ))
+ .color_range(2, ..20 + search_term_len)
+ .color_range(3, 20..20 + search_term_len)
+ .color_range(3, 22 + search_term_len..)
+ };
+ print_text_with_coordinates(layout_indication_line, x, y + 1, None, None);
+ println!();
+ let mut table = Table::new();
+ for (i, (layout_info, indices, is_selected)) in
+ new_session_info.layouts_to_render().into_iter().enumerate()
+ {
+ let layout_name = layout_info.name();
+ let layout_name_len = layout_name.width();
+ let is_builtin = layout_info.is_builtin();
+ if i > max_rows_of_new_session_block {
+ break;
+ } else {
+ let mut layout_cell = if is_builtin {
+ Text::new(format!("{} (built-in)", layout_name))
+ .color_range(1, 0..layout_name_len)
+ .color_range(0, layout_name_len + 1..)
+ .color_indices(3, indices)
+ } else {
+ Text::new(format!("{}", layout_name))
+ .color_range(1, ..)
+ .color_indices(3, indices)
+ };
+ if is_selected {
+ layout_cell = layout_cell.selected();
+ }
+ let arrow_cell = if is_selected {
+ Text::new(format!("<↓↑>")).selected().color_range(3, ..)
+ } else {
+ Text::new(format!(" ")).color_range(3, ..)
+ };
+ table = table.add_styled_row(vec![arrow_cell, layout_cell]);
+ }
+ }
+ print_table_with_coordinates(table, x, y + 3, None, None);
+}
+
+pub fn render_error(error_text: &str, rows: usize, columns: usize, x: usize, y: usize) {
print_text_with_coordinates(
Text::new(format!("Error: {}", error_text)).color_range(3, ..),
- 0,
- rows,
+ x,
+ y + rows,
Some(columns),
None,
);
}
-pub fn render_renaming_session_screen(new_session_name: &str, rows: usize, columns: usize) {
+pub fn render_renaming_session_screen(
+ new_session_name: &str,
+ rows: usize,
+ columns: usize,
+ x: usize,
+ y: usize,
+) {
if rows == 0 || columns == 0 {
return;
}
- let prompt_text = "NEW NAME FOR CURRENT SESSION";
- let new_session_name = format!("{}_", new_session_name);
- let prompt_y_location = (rows / 2).saturating_sub(1);
- let session_name_y_location = (rows / 2) + 1;
- let prompt_x_location = columns.saturating_sub(prompt_text.chars().count()) / 2;
- let session_name_x_location = columns.saturating_sub(new_session_name.chars().count()) / 2;
- print_text_with_coordinates(
- Text::new(prompt_text).color_range(0, ..),
- prompt_x_location,
- prompt_y_location,
- None,
- None,
- );
- print_text_with_coordinates(
- Text::new(new_session_name).color_range(3, ..),
- session_name_x_location,
- session_name_y_location,
- None,
- None,
+ let text = Text::new(format!(
+ "New name for current session: {}_ (<ENTER> when done)",
+ new_session_name
+ ))
+ .color_range(2, ..29)
+ .color_range(
+ 3,
+ 33 + new_session_name.width()..40 + new_session_name.width(),
);
+ print_text_with_coordinates(text, x, y, None, None);
}
-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 rename = colors.magenta("<Ctrl r>");
- let rename_text = colors.bold("Rename session");
- let enter = colors.magenta("<ENTER>");
- let select = colors.bold("Switch to selected");
- let esc = colors.magenta("<ESC>");
- let to_hide = colors.bold("Hide");
+pub fn render_controls_line(
+ active_screen: ActiveScreen,
+ max_cols: usize,
+ colors: Colors,
+ x: usize,
+ y: usize,
+) {
+ match active_screen {
+ ActiveScreen::NewSession => {
+ if max_cols >= 50 {
+ print!(
+ "\u{1b}[m\u{1b}[{y};{x}H\u{1b}[1mHelp: Fill in the form to start a new session."
+ );
+ }
+ },
+ ActiveScreen::AttachToSession => {
+ let arrows = colors.magenta("<←↓↑→>");
+ let navigate = colors.bold("Navigate");
+ let enter = colors.magenta("<ENTER>");
+ let select = colors.bold("Attach");
+ let rename = colors.magenta("<Ctrl r>");
+ let rename_text = colors.bold("Rename");
+ let disconnect = colors.magenta("<Ctrl x>");
+ let disconnect_text = colors.bold("Disconnect others");
+ let kill = colors.magenta("<Del>");
+ let kill_text = colors.bold("Kill");
+ let kill_all = colors.magenta("<Ctrl d>");
+ let kill_all_text = colors.bold("Kill all");
- if max_cols >= 104 {
- print!(
- "\u{1b}[m\u{1b}[{row}HHelp: {arrows} - {navigate}, {enter} - {select}, {rename} - {rename_text}, {esc} - {to_hide}"
- );
- } else if max_cols >= 73 {
- let navigate = colors.bold("Navigate");
- let select = colors.bold("Switch");
- let rename_text = colors.bold("Rename");
- print!(
- "\u{1b}[m\u{1b}[{row}HHelp: {arrows} - {navigate}, {enter} - {select}, {rename} - {rename_text}, {esc} - {to_hide}"
- );
- } else if max_cols >= 28 {
- print!("\u{1b}[m\u{1b}[{row}H{arrows}/{enter}/{rename}/{esc}");
+ if max_cols > 90 {
+ print!(
+ "\u{1b}[m\u{1b}[{y};{x}HHelp: {rename} - {rename_text}, {disconnect} - {disconnect_text}, {kill} - {kill_text}, {kill_all} - {kill_all_text}"
+ );
+ } else if max_cols >= 28 {
+ print!("\u{1b}[m\u{1b}[{y};{x}H{rename}/{disconnect}/{kill}/{kill_all}");
+ }
+ },
+ ActiveScreen::ResurrectSession => {
+ let arrows = colors.magenta("<↓↑>");
+ let navigate = colors.bold("Navigate");
+ let enter = colors.magenta("<ENTER>");
+ let select = colors.bold("Resurrect");
+ let del = colors.magenta("<DEL>");
+ let del_text = colors.bold("Delete");
+ let del_all = colors.magenta("<Ctrl d>");
+ let del_all_text = colors.bold("Delete all");
+
+ if max_cols > 83 {
+ print!(
+ "\u{1b}[m\u{1b}[{y};{x}HHelp: {arrows} - {navigate}, {enter} - {select}, {del} - {del_text}, {del_all} - {del_all_text}"
+ );
+ } else if max_cols >= 28 {
+ print!("\u{1b}[m\u{1b}[{y};{x}H{arrows}/{enter}/{del}/{del_all}");
+ }
+ },
}
}
diff --git a/default-plugins/session-manager/src/ui/mod.rs b/default-plugins/session-manager/src/ui/mod.rs
index 94a1a8a7..b86db946 100644
--- a/default-plugins/session-manager/src/ui/mod.rs
+++ b/default-plugins/session-manager/src/ui/mod.rs
@@ -1,4 +1,5 @@
pub mod components;
+pub mod welcome_screen;
use zellij_tile::prelude::*;
use crate::session_list::{SelectedIndex, SessionList};
@@ -29,7 +30,7 @@ macro_rules! render_assets {
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.make_selected(true);
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
@@ -76,8 +77,10 @@ impl SessionList {
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 {
+ let mut render_arrows = true;
for line_to_render in result_lines.iter_mut() {
- line_to_render.make_selected();
+ line_to_render.make_selected_as_search(render_arrows);
+ render_arrows = false; // only render arrows on the first search result
}
}
lines_to_render.append(&mut result_lines);
diff --git a/default-plugins/session-manager/src/ui/welcome_screen.rs b/default-plugins/session-manager/src/ui/welcome_screen.rs
new file mode 100644
index 00000000..ccf20c50
--- /dev/null
+++ b/default-plugins/session-manager/src/ui/welcome_screen.rs
@@ -0,0 +1,168 @@
+static BANNER: &str = "
+██╗ ██╗██╗ ███████╗██████╗ ██████╗ ███╗ ███╗ ███████╗███████╗██╗ ██╗ ██╗ ██╗██╗
+██║ ██║██║ ██╔════╝██╔══██╗██╔═══██╗████╗ ████║ ╚══███╔╝██╔════╝██║ ██║ ██║ ██║██║
+███████║██║ █████╗ ██████╔╝██║ ██║██╔████╔██║ ███╔╝ █████╗ ██║ ██║ ██║ ██║██║
+██╔══██║██║ ██╔══╝ ██╔══██╗██║ ██║██║╚██╔╝██║ ███╔╝ ██╔══╝ ██║ ██║ ██║██ ██║╚═╝
+██║ ██║██║ ██║ ██║ ██║╚██████╔╝██║ ╚═╝ ██║ ███████╗███████╗███████╗███████╗██║╚█████╔╝██╗
+╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚══════╝╚══════╝╚══════╝╚══════╝╚═╝ ╚════╝ ╚═╝
+";
+
+static SMALL_BANNER: &str = "
+██╗ ██╗██╗ ██╗
+██║ ██║██║ ██║
+███████║██║ ██║
+██╔══██║██║ ╚═╝
+██║ ██║██║ ██╗
+╚═╝ ╚═╝╚═╝ ╚═╝
+";
+
+static MEDIUM_BANNER: &str = "
+██╗ ██╗██╗ ████████╗██╗ ██╗███████╗██████╗ ███████╗ ██╗
+██║ ██║██║ ╚══██╔══╝██║ ██║██╔════╝██╔══██╗██╔════╝ ██║
+███████║██║ ██║ ███████║█████╗ ██████╔╝█████╗ ██║
+██╔══██║██║ ██║ ██╔══██║██╔══╝ ██╔══██╗██╔══╝ ╚═╝
+██║ ██║██║ ██║ ██║ ██║███████╗██║ ██║███████╗ ██╗
+╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚══════╝ ╚═╝
+";
+
+pub fn render_banner(x: usize, y: usize, rows: usize, cols: usize) {
+ if rows >= 8 {
+ if cols > 100 {
+ println!("\u{1b}[{}H", y + rows.saturating_sub(8) / 2);
+ for line in BANNER.lines() {
+ println!("\u{1b}[{}C{}", x.saturating_sub(1), line);
+ }
+ } else if cols > 63 {
+ println!("\u{1b}[{}H", y + rows.saturating_sub(8) / 2);
+ let x = (cols.saturating_sub(63) as f64 / 2.0) as usize;
+ for line in MEDIUM_BANNER.lines() {
+ println!("\u{1b}[{}C{}", x, line);
+ }
+ } else {
+ println!("\u{1b}[{}H", y + rows.saturating_sub(8) / 2);
+ let x = (cols.saturating_sub(18) as f64 / 2.0) as usize;
+ for line in SMALL_BANNER.lines() {
+ println!("\u{1b}[{}C{}", x, line);
+ }
+ }
+ } else if rows > 2 {
+ println!(
+ "\u{1b}[{};{}H\u{1b}[1mHi from Zellij!",
+ (y + rows / 2) + 1,
+ (x + cols.saturating_sub(15) / 2).saturating_sub(1)
+ );
+ }
+}
+
+pub fn render_welcome_boundaries(rows: usize, cols: usize) {
+ let width_of_main_menu = std::cmp::min(cols, 101);
+ let has_room_for_logos = cols.saturating_sub(width_of_main_menu) > 100;
+ let left_boundary_x = (cols.saturating_sub(width_of_main_menu) as f64 / 2.0).floor() as usize;
+ let right_boundary_x = left_boundary_x + width_of_main_menu;
+ let y_starting_point = rows.saturating_sub(15) / 2;
+ let middle_row =
+ (y_starting_point + rows.saturating_sub(y_starting_point) / 2).saturating_sub(1);
+ for i in y_starting_point..rows {
+ if i == middle_row {
+ if has_room_for_logos {
+ print!("\u{1b}[{};{}H┤", i + 1, left_boundary_x + 1);
+ print!(
+ "\u{1b}[m\u{1b}[{};{}H├\u{1b}[K",
+ i + 1,
+ right_boundary_x + 1
+ );
+ print!("\u{1b}[{};{}H", i + 1, left_boundary_x.saturating_sub(9));
+ for _ in 0..10 {
+ print!("─");
+ }
+ print!("\u{1b}[{};{}H", i + 1, right_boundary_x + 2);
+ for _ in 0..10 {
+ print!("─");
+ }
+ } else {
+ print!("\u{1b}[{};{}H│", i + 1, left_boundary_x + 1);
+ print!(
+ "\u{1b}[m\u{1b}[{};{}H│\u{1b}[K",
+ i + 1,
+ right_boundary_x + 1
+ );
+ }
+ } else {
+ if i == y_starting_point {
+ print!("\u{1b}[{};{}H┌", i + 1, left_boundary_x + 1);
+ print!(
+ "\u{1b}[m\u{1b}[{};{}H┐\u{1b}[K",
+ i + 1,
+ right_boundary_x + 1
+ );
+ } else if i == rows.saturating_sub(1) {
+ print!("\u{1b}[{};{}H└", i + 1, left_boundary_x + 1);
+ print!(
+ "\u{1b}[m\u{1b}[{};{}H┘\u{1b}[K",
+ i + 1,
+ right_boundary_x + 1
+ );
+ } else {
+ print!("\u{1b}[{};{}H│", i + 1, left_boundary_x + 1);
+ print!(
+ "\u{1b}[m\u{1b}[{};{}H│\u{1b}[K",
+ i + 1,
+ right_boundary_x + 1
+ ); // this includes some
+ // ANSI magic to delete
+ // everything after this
+ // boundary in order to
+ // fix some rendering
+ // bugs in the legacy
+ // components of this
+ // plugin
+ }
+ }
+ }
+ if rows.saturating_sub(y_starting_point) > 25 && has_room_for_logos {
+ for (i, line) in LOGO.lines().enumerate() {
+ print!(
+ "\u{1b}[{};{}H{}",
+ middle_row.saturating_sub(12) + i,
+ 0,
+ line
+ );
+ }
+ for (i, line) in LOGO.lines().enumerate() {
+ print!(
+ "\u{1b}[{};{}H{}",
+ middle_row.saturating_sub(12) + i,
+ cols.saturating_sub(47),
+ line
+ );
+ }
+ }
+}
+static LOGO: &str = r#" 
+ 
+  _y$ y@g_
+  ya@@@@L4@@@@gy_
+  u@@@@@@F "@@@@@@@@y_
+  _a@@, @@@P~` __ ~T@@@@@@@@g
+  _yg@@@@@$ "~ _yg@@@@gy `~PR@F~_yggy_
+  y$@@@@@@PF _y$@@@@@@@@@@gy_ 4@@@@@@@y_
+  g@@@@@F~ _yg@@@@@@@@@@@@@@@@@@g_ ~F@@@@@@
+  $@@@F yg@@@@@@@@@@@@@@@@@@@@@@@@gy 9@@F"
+  $@@@$ 4@@@@@@~@@@@@@@@@@@@@@@@@@@@@ "_yg$
+  $@@@$ 4@@@@@ ~@@@@@@@@@@@@@@@@@@@ g@@@@
+  $@@@$ 4@@@@@@y ~@@@@@@@@@@@@@@@@@ $@@@F
+  $@@@$ 4@@@@@@@@y ~@@@@@@@@@@@@@@@ `~_y_
+  $@@@$ 4@@@@@@@P` _g@@@@@@@@@@@@@@@ y@@@@
+  $@@@$ 4@@@@@P` _y@@@@@@@@@@@@@@@@@ 4@@@@
+  $@@@$ 4@@@@$_ y@@@@@ $@@@@ 4@@@@
+  $@@@$ 4@@@@@@g@@@@@@@@@@@@@@@@@@@@@ ~@@@
+  $@@P 7R@@@@@@@@@@@@@@@@@@@@@@@@P~ _@g_7@
+  ~~_yg@gy_ ~5@@@@@@@@@@@@@@@@@@F~ _yg@@@@y
+  R@@@@@@@ ~~@@@@@@@@@@@P~` ya@@@@@@@@F
+  ~5@@@@ 4@gy `~4@@@@F~ y@@@@@@@P~`
+  `~P 4@@@@gy_ `` _yr a@@@@@F~
+  5@@@@@@@gg@@~_g@@@~`
+  ~~@@@@@@@^y@F~
+  `~4@F ~
+ 
+  "#;