diff options
| author | Aram Drevekenin <aram@poor.dev> | 2026-01-29 11:53:56 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-01-29 11:53:56 +0100 |
| commit | cdbdf2b6679c580d4b0c8332694cd8ee9117d49f (patch) | |
| tree | f262277077de2891c3952663f69f6906356c4e04 /default-plugins/session-manager | |
| parent | fix: session creation time on musl (for "zellij ls") (#4645) (diff) | |
| download | sl0ppij-cdbdf2b6679c580d4b0c8332694cd8ee9117d49f.tar.gz sl0ppij-cdbdf2b6679c580d4b0c8332694cd8ee9117d49f.tar.xz sl0ppij-cdbdf2b6679c580d4b0c8332694cd8ee9117d49f.zip | |
Allow saving current session for resurrection on demand (#4654)
* initial implementation
* session manager ui and plugin api
* remove event
* cleanups and formatting
* add pr
Diffstat (limited to 'default-plugins/session-manager')
| -rw-r--r-- | default-plugins/session-manager/src/main.rs | 45 | ||||
| -rw-r--r-- | default-plugins/session-manager/src/ui/components.rs | 115 |
2 files changed, 154 insertions, 6 deletions
diff --git a/default-plugins/session-manager/src/main.rs b/default-plugins/session-manager/src/main.rs index c89b0049..1a76fdb9 100644 --- a/default-plugins/session-manager/src/main.rs +++ b/default-plugins/session-manager/src/main.rs @@ -10,7 +10,7 @@ use new_session_info::NewSessionInfo; use ui::{ components::{ render_controls_line, render_error, render_new_session_block, render_prompt, - render_renaming_session_screen, render_screen_toggle, Colors, + render_renaming_session_screen, render_screen_toggle, render_unsaved_changes_line, Colors, }, welcome_screen::{render_banner, render_welcome_boundaries}, SessionUiInfo, @@ -19,7 +19,7 @@ use ui::{ use resurrectable_sessions::ResurrectableSessions; use session_list::SessionList; -#[derive(Clone, Debug, Copy)] +#[derive(Clone, Debug, Copy, PartialEq)] enum ActiveScreen { NewSession, AttachToSession, @@ -47,6 +47,7 @@ struct State { show_kill_all_sessions_warning: bool, request_ids: Vec<String>, is_web_client: bool, + current_session_last_saved_time: Option<u64>, } register_plugin!(State); @@ -61,11 +62,16 @@ impl ZellijPlugin for State { self.active_screen = ActiveScreen::NewSession; } self.new_session_info.is_welcome_screen = self.is_welcome_screen; + + self.current_session_last_saved_time = current_session_last_saved_time(); + set_timeout(1.0); + subscribe(&[ EventType::ModeUpdate, EventType::SessionUpdate, EventType::Key, EventType::RunCommandResult, + EventType::Timer, ]); } @@ -94,6 +100,11 @@ impl ZellijPlugin for State { fn update(&mut self, event: Event) -> bool { let mut should_render = false; match event { + Event::Timer(_) => { + self.current_session_last_saved_time = current_session_last_saved_time(); + set_timeout(1.0); + should_render = true; + }, Event::ModeUpdate(mode_info) => { self.colors = Colors::new(mode_info.style.colors); self.is_web_client = mode_info.is_web_client.unwrap_or(false); @@ -156,7 +167,8 @@ impl ZellijPlugin for State { 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; + let bottom_lines = 7; + let room_for_list = height.saturating_sub(bottom_lines); self.sessions.update_rows(room_for_list); let list = self.sessions @@ -172,8 +184,25 @@ impl ZellijPlugin for State { } if let Some(error) = self.error.as_ref() { render_error(&error, height, width, x, y); + } else if self.active_screen == ActiveScreen::AttachToSession && !self.is_welcome_screen { + let help_offset = render_controls_line( + self.active_screen, + width, + self.colors, + x, + rows.saturating_sub(1), + ); + // Adjust position and width based on whether controls line shows "Help: " + let adjusted_x = x + help_offset; + let adjusted_width = width.saturating_sub(help_offset); + render_unsaved_changes_line( + adjusted_width, + adjusted_x, + rows, + self.current_session_last_saved_time, + ); } else { - render_controls_line(self.active_screen, width, self.colors, x + 1, rows); + let _ = render_controls_line(self.active_screen, width, self.colors, x, rows); } if self.is_welcome_screen { render_welcome_boundaries(rows, cols); // explicitly done in the end to override some @@ -395,6 +424,14 @@ impl State { hide_self(); } }, + BareKey::Char('a') if key.has_modifiers(&[KeyModifier::Ctrl]) => { + if !self.is_welcome_screen { + // we don't want to save welcome screen sessions + if let Err(e) = save_session() { + self.show_error(&format!("Couldn't save session: {}", e)); + } + } + }, _ => {}, } } diff --git a/default-plugins/session-manager/src/ui/components.rs b/default-plugins/session-manager/src/ui/components.rs index dede06d9..8310d2d3 100644 --- a/default-plugins/session-manager/src/ui/components.rs +++ b/default-plugins/session-manager/src/ui/components.rs @@ -1,4 +1,6 @@ +use humantime::format_duration; use std::path::PathBuf; +use std::time::Duration; use unicode_width::UnicodeWidthChar; use unicode_width::UnicodeWidthStr; use zellij_tile::prelude::*; @@ -927,13 +929,19 @@ pub fn render_controls_line( colors: Colors, x: usize, y: usize, -) { - match active_screen { +) -> usize { + const HELP_PREFIX_LEN: usize = 6; + let x = x + 1; + + let shows_help_prefix = 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." ); + true + } else { + false } }, ActiveScreen::AttachToSession => { @@ -950,8 +958,12 @@ pub fn render_controls_line( print!( "\u{1b}[m\u{1b}[{y};{x}HHelp: {rename} - {rename_text}, {disconnect} - {disconnect_text}, {kill} - {kill_text}, {kill_all} - {kill_all_text}" ); + true } else if max_cols >= 28 { print!("\u{1b}[m\u{1b}[{y};{x}H{rename}/{disconnect}/{kill}/{kill_all}"); + false + } else { + false } }, ActiveScreen::ResurrectSession => { @@ -968,13 +980,112 @@ pub fn render_controls_line( print!( "\u{1b}[m\u{1b}[{y};{x}HHelp: {arrows} - {navigate}, {enter} - {select}, {del} - {del_text}, {del_all} - {del_all_text}" ); + true } else if max_cols >= 28 { print!("\u{1b}[m\u{1b}[{y};{x}H{arrows}/{enter}/{del}/{del_all}"); + false + } else { + false } }, + }; + + if shows_help_prefix { + HELP_PREFIX_LEN + } else { + 0 + } +} + +fn format_elapsed_time(elapsed_millis: u64) -> String { + // Convert elapsed milliseconds to Duration + let elapsed_duration = Duration::from_millis(elapsed_millis); + + // Use humantime to format the duration, same as in resurrectable_sessions.rs + let duration_str = format_duration(elapsed_duration).to_string(); + let duration_parts = duration_str.split_whitespace(); + let mut formatted_duration = String::new(); + for part in duration_parts { + if !part.ends_with('s') { + if !formatted_duration.is_empty() { + formatted_duration.push(' '); + } + formatted_duration.push_str(part); + } + } + if formatted_duration.is_empty() { + format!("just now") + } else { + format!("{} ago", formatted_duration) } } +pub fn render_unsaved_changes_line( + max_cols: usize, + x: usize, + y: usize, + last_saved_timestamp: Option<u64>, +) { + // Declare all text components + let shortcut_text = "<Ctrl a>"; + let full_action_text = "Save current session for resurrection"; + let medium_action_text = "Save session"; + let short_action_text = "Save"; + let separator = " - "; + let space = " "; + + let time_text = match last_saved_timestamp { + Some(timestamp) => { + let elapsed = format_elapsed_time(timestamp); + format!("(saved {})", elapsed) + }, + None => "(not saved)".to_string(), + }; + + // Calculate component widths + let shortcut_width = shortcut_text.width(); + let separator_width = separator.width(); + let space_width = space.width(); + let time_width = time_text.width(); + + // Calculate total widths for each display mode + // Format: "{shortcut}{separator}{action}{space}{time}" + let full_width = + shortcut_width + separator_width + full_action_text.width() + space_width + time_width; + let medium_width = + shortcut_width + separator_width + medium_action_text.width() + space_width + time_width; + let short_width = + shortcut_width + separator_width + short_action_text.width() + space_width + time_width; + let minimal_width = shortcut_width + space_width + time_width; + + // Select appropriate message based on available width + let msg = if max_cols >= full_width { + format!( + "{}{}{}{}{}", + shortcut_text, separator, full_action_text, space, time_text + ) + } else if max_cols >= medium_width { + format!( + "{}{}{}{}{}", + shortcut_text, separator, medium_action_text, space, time_text + ) + } else if max_cols >= short_width { + format!( + "{}{}{}{}{}", + shortcut_text, separator, short_action_text, space, time_text + ) + } else if max_cols >= minimal_width { + format!("{}{}{}", shortcut_text, space, time_text) + } else { + return; // Not enough space to render + }; + + let text = Text::new(&msg) + .color_substring(3, shortcut_text) + .color_substring(2, &time_text); + print_text_with_coordinates(text, x, y, None, None); +} + // Maps the various prompts and UI elements to the colors to present them with // // Since this plugin predates the UI components, this is a developer |
