summaryrefslogtreecommitdiffstats
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
parent286f7ccc28e3a091e2d3ef5663ae7878056551e6 (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
-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.rs384
-rw-r--r--default-plugins/session-manager/src/ui/mod.rs7
-rw-r--r--default-plugins/session-manager/src/ui/welcome_screen.rs168
-rw-r--r--src/commands.rs71
-rw-r--r--src/sessions.rs11
-rw-r--r--zellij-server/src/lib.rs35
-rw-r--r--zellij-server/src/plugins/unit/plugin_tests.rs240
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__disconnect_other_clients_plugins_command.snap10
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__switch_session_plugin_command.snap18
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__switch_session_with_layout_plugin_command.snap22
-rw-r--r--zellij-server/src/plugins/zellij_exports.rs41
-rw-r--r--zellij-server/src/screen.rs21
-rw-r--r--zellij-server/src/tab/mod.rs2
-rw-r--r--zellij-server/src/unit/screen_tests.rs12
-rw-r--r--zellij-tile/src/shim.rs33
-rw-r--r--zellij-utils/assets/layouts/welcome.kdl8
-rw-r--r--zellij-utils/assets/prost/api.event.rs10
-rw-r--r--zellij-utils/assets/prost/api.plugin_command.rs18
-rw-r--r--zellij-utils/src/data.rs45
-rw-r--r--zellij-utils/src/errors.rs1
-rw-r--r--zellij-utils/src/input/config.rs9
-rw-r--r--zellij-utils/src/input/keybinds.rs11
-rw-r--r--zellij-utils/src/input/layout.rs122
-rw-r--r--zellij-utils/src/kdl/mod.rs50
-rw-r--r--zellij-utils/src/kdl/snapshots/zellij_utils__kdl__serialize_and_deserialize_session_info.snap4
-rw-r--r--zellij-utils/src/kdl/snapshots/zellij_utils__kdl__serialize_and_deserialize_session_info_with_data.snap7
-rw-r--r--zellij-utils/src/plugin_api/event.proto6
-rw-r--r--zellij-utils/src/plugin_api/event.rs57
-rw-r--r--zellij-utils/src/plugin_api/plugin_command.proto8
-rw-r--r--zellij-utils/src/plugin_api/plugin_command.rs30
-rw-r--r--zellij-utils/src/setup.rs71
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