diff options
| author | Aram Drevekenin <aram@poor.dev> | 2025-02-13 17:12:47 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-02-13 17:12:47 +0100 |
| commit | 7aaa7aea8f9a378660492d03ff5736dbf846978d (patch) | |
| tree | 2b9884e135d73c82da442d1de649eb2b5df3fc54 /default-plugins | |
| parent | docs(changelog): about plugin (diff) | |
| download | sl0ppij-7aaa7aea8f9a378660492d03ff5736dbf846978d.tar.gz sl0ppij-7aaa7aea8f9a378660492d03ff5736dbf846978d.tar.xz sl0ppij-7aaa7aea8f9a378660492d03ff5736dbf846978d.zip | |
feat(ux): tips on startup (#3988)
* add some tips
* refactoring and max tip index
* add config to disable release notes and startup tips
* allow dismissing startup tips from plugin
* random tip at startup
* custom launch about
* fix tests
* style(fmt): rustfmt
Diffstat (limited to 'default-plugins')
| -rw-r--r-- | default-plugins/about/Cargo.toml | 3 | ||||
| -rw-r--r-- | default-plugins/about/src/main.rs | 118 | ||||
| -rw-r--r-- | default-plugins/about/src/pages.rs | 72 | ||||
| -rw-r--r-- | default-plugins/about/src/tips.rs | 852 | ||||
| -rw-r--r-- | default-plugins/configuration/src/presets.rs | 42 | ||||
| -rw-r--r-- | default-plugins/status-bar/src/one_line_ui.rs | 20 |
6 files changed, 1088 insertions, 19 deletions
diff --git a/default-plugins/about/Cargo.toml b/default-plugins/about/Cargo.toml index 66a79ac2..536617d6 100644 --- a/default-plugins/about/Cargo.toml +++ b/default-plugins/about/Cargo.toml @@ -6,6 +6,5 @@ edition = "2021" license = "MIT" [dependencies] -ansi_term = "0.12.1" zellij-tile = { path = "../../zellij-tile" } -chrono = "0.4.0" +rand = "0.9.0" diff --git a/default-plugins/about/src/main.rs b/default-plugins/about/src/main.rs index bd7c28ea..87af9ffd 100644 --- a/default-plugins/about/src/main.rs +++ b/default-plugins/about/src/main.rs @@ -1,11 +1,15 @@ mod active_component; mod pages; +mod tips; use zellij_tile::prelude::*; use pages::Page; +use rand::prelude::*; +use rand::rng; use std::cell::RefCell; use std::collections::BTreeMap; use std::rc::Rc; +use tips::MAX_TIP_INDEX; const UI_ROWS: usize = 20; const UI_COLUMNS: usize = 90; @@ -20,6 +24,10 @@ struct App { tab_columns: usize, own_plugin_id: Option<u32>, is_release_notes: bool, + is_startup_tip: bool, + tip_index: usize, + waiting_for_config_to_be_written: bool, + error: Option<String>, } impl Default for App { @@ -32,6 +40,7 @@ impl Default for App { link_executable.clone(), "".to_owned(), base_mode.clone(), + false, ), link_executable, zellij_version, @@ -40,6 +49,10 @@ impl Default for App { tab_columns: 0, own_plugin_id: None, is_release_notes: false, + is_startup_tip: false, + tip_index: 0, + waiting_for_config_to_be_written: false, + error: None, } } } @@ -52,27 +65,62 @@ impl ZellijPlugin for App { .get("is_release_notes") .map(|v| v == "true") .unwrap_or(false); + self.is_startup_tip = configuration + .get("is_startup_tip") + .map(|v| v == "true") + .unwrap_or(false); subscribe(&[ EventType::Key, EventType::Mouse, EventType::ModeUpdate, EventType::RunCommandResult, EventType::TabUpdate, + EventType::FailedToWriteConfigToDisk, + EventType::ConfigWasWrittenToDisk, ]); let own_plugin_id = get_plugin_ids().plugin_id; self.own_plugin_id = Some(own_plugin_id); *self.zellij_version.borrow_mut() = get_zellij_version(); self.change_own_title(); self.query_link_executable(); - self.active_page = Page::new_main_screen( - self.link_executable.clone(), - self.zellij_version.borrow().clone(), - self.base_mode.clone(), - ); + self.active_page = if self.is_startup_tip { + let mut rng = rng(); + self.tip_index = rng.random_range(0..=MAX_TIP_INDEX); + Page::new_tip_screen( + self.link_executable.clone(), + self.base_mode.clone(), + self.tip_index, + ) + } else { + Page::new_main_screen( + self.link_executable.clone(), + self.zellij_version.borrow().clone(), + self.base_mode.clone(), + self.is_release_notes, + ) + }; } fn update(&mut self, event: Event) -> bool { let mut should_render = false; match event { + Event::FailedToWriteConfigToDisk(file_path) => { + if self.waiting_for_config_to_be_written { + let error = match file_path { + Some(file_path) => { + format!("Failed to write config to disk at: {}", file_path) + }, + None => format!("Failed to write config to disk."), + }; + eprintln!("{}", error); + self.error = Some(error); + should_render = true; + } + }, + Event::ConfigWasWrittenToDisk => { + if self.waiting_for_config_to_be_written { + close_self(); + } + }, Event::TabUpdate(tab_info) => { self.center_own_pane(tab_info); }, @@ -98,14 +146,19 @@ impl ZellijPlugin for App { } }, Event::Key(key) => { - should_render = self.handle_key(key); + if let Some(_error) = self.error.take() { + // dismiss error on any key + should_render = true; + } else { + should_render = self.handle_key(key); + } }, _ => {}, } should_render } fn render(&mut self, rows: usize, cols: usize) { - self.active_page.render(rows, cols); + self.active_page.render(rows, cols, &self.error); } } @@ -162,11 +215,24 @@ impl App { } pub fn handle_key(&mut self, key: KeyWithModifier) -> bool { let mut should_render = false; - if key.bare_key == BareKey::Enter && key.has_no_modifiers() { + if key.bare_key == BareKey::Up && key.has_no_modifiers() && self.is_startup_tip { + self.previous_tip(); + should_render = true; + } else if key.bare_key == BareKey::Down && key.has_no_modifiers() && self.is_startup_tip { + self.next_tip(); + should_render = true; + } else if key.bare_key == BareKey::Enter && key.has_no_modifiers() { if let Some(new_page) = self.active_page.handle_selection() { self.active_page = new_page; should_render = true; } + } else if key.bare_key == BareKey::Char('c') + && key.has_modifiers(&[KeyModifier::Ctrl]) + && self.is_startup_tip + { + self.waiting_for_config_to_be_written = true; + let save_configuration = true; + reconfigure("show_startup_tips false".to_owned(), save_configuration); } else if key.bare_key == BareKey::Esc && key.has_no_modifiers() { if self.active_page.is_main_screen { close_self(); @@ -175,9 +241,21 @@ impl App { self.link_executable.clone(), self.zellij_version.borrow().clone(), self.base_mode.clone(), + self.is_release_notes, ); should_render = true; } + } else if key.bare_key == BareKey::Char('?') + && !self.is_release_notes + && !self.is_startup_tip + { + self.is_startup_tip = true; + self.active_page = Page::new_tip_screen( + self.link_executable.clone(), + self.base_mode.clone(), + self.tip_index, + ); + should_render = true; } else { should_render = self.active_page.handle_key(key); } @@ -208,4 +286,28 @@ impl App { } } } + fn previous_tip(&mut self) { + if self.tip_index == 0 { + self.tip_index = MAX_TIP_INDEX; + } else { + self.tip_index = self.tip_index.saturating_sub(1); + } + self.active_page = Page::new_tip_screen( + self.link_executable.clone(), + self.base_mode.clone(), + self.tip_index, + ); + } + fn next_tip(&mut self) { + if self.tip_index == MAX_TIP_INDEX { + self.tip_index = 0; + } else { + self.tip_index += 1; + } + self.active_page = Page::new_tip_screen( + self.link_executable.clone(), + self.base_mode.clone(), + self.tip_index, + ); + } } diff --git a/default-plugins/about/src/pages.rs b/default-plugins/about/src/pages.rs index 2951c1b0..9bf82200 100644 --- a/default-plugins/about/src/pages.rs +++ b/default-plugins/about/src/pages.rs @@ -20,10 +20,11 @@ impl Page { link_executable: Rc<RefCell<String>>, zellij_version: String, base_mode: Rc<RefCell<InputMode>>, + is_release_notes: bool, ) -> Self { Page::new() .main_screen() - .with_title(main_screen_title(zellij_version.clone())) + .with_title(main_screen_title(zellij_version.clone(), is_release_notes)) .with_bulletin_list(BulletinList::new(whats_new_title()).with_items(vec![ ActiveComponent::new(TextOrCustomRender::Text(main_menu_item( "Stacked Resize", @@ -102,9 +103,15 @@ impl Page { link_executable.clone(), )), ])]) - .with_help(Box::new(|hovering_over_link, menu_item_is_selected| { - main_screen_help_text(hovering_over_link, menu_item_is_selected) - })) + .with_help(if is_release_notes { + Box::new(|hovering_over_link, menu_item_is_selected| { + release_notes_main_help(hovering_over_link, menu_item_is_selected) + }) + } else { + Box::new(|hovering_over_link, menu_item_is_selected| { + main_screen_help_text(hovering_over_link, menu_item_is_selected) + }) + }) } pub fn new_stacked_resize(link_executable: Rc<RefCell<String>>) -> Page { Page::new() @@ -543,7 +550,7 @@ impl Page { row_count += self.components_to_render.len(); row_count } - pub fn render(&mut self, rows: usize, columns: usize) { + pub fn render(&mut self, rows: usize, columns: usize, error: &Option<String>) { let base_x = columns.saturating_sub(self.ui_column_count()) / 2; let base_y = rows.saturating_sub(self.ui_row_count()) / 2; let mut current_y = base_y; @@ -562,12 +569,23 @@ impl Page { RenderedComponent::HelpText(_) => true, _ => false, }; + if is_help { + if let Some(error) = error { + render_error(error, rows); + continue; + } + } let y = if is_help { rows } else { current_y }; + let columns = if is_help { + columns + } else { + columns.saturating_sub(base_x * 2) + }; let rendered_rows = rendered_component.render( base_x, y, rows, - columns.saturating_sub(base_x * 2), + columns, self.hovering_over_link, self.menu_item_is_selected, ); @@ -576,6 +594,16 @@ impl Page { } } +fn render_error(error: &str, y: usize) { + print_text_with_coordinates( + Text::new(format!("ERROR: {}", error)).color_range(3, ..), + 0, + y, + None, + None, + ); +} + fn changelog_link_unselected(version: String) -> Text { let full_changelog_text = format!( "https://github.com/zellij-org/zellij/releases/tag/v{}", @@ -647,9 +675,14 @@ fn whats_new_title() -> Text { Text::new("What's new?") } -fn main_screen_title(version: String) -> Text { - let title_text = format!("Hi there, welcome to Zellij {}!", &version); - Text::new(title_text).color_range(2, 21..=27 + version.chars().count()) +fn main_screen_title(version: String, is_release_notes: bool) -> Text { + if is_release_notes { + let title_text = format!("Hi there, welcome to Zellij {}!", &version); + Text::new(title_text).color_range(2, 21..=27 + version.chars().count()) + } else { + let title_text = format!("Zellij {}", &version); + Text::new(title_text).color_range(2, ..) + } } fn main_screen_help_text(hovering_over_link: bool, menu_item_is_selected: bool) -> Text { @@ -665,6 +698,27 @@ fn main_screen_help_text(hovering_over_link: bool, menu_item_is_selected: bool) .color_range(1, 23..=29) .color_range(1, 45..=49) } else { + let help_text = format!("Help: <↓↑> - Navigate, <ESC> - Dismiss, <?> - Usage Tips"); + Text::new(help_text) + .color_range(1, 6..=9) + .color_range(1, 23..=27) + .color_range(1, 40..=42) + } +} + +fn release_notes_main_help(hovering_over_link: bool, menu_item_is_selected: bool) -> Text { + if hovering_over_link { + let help_text = format!("Help: Click or Shift-Click to open in browser"); + Text::new(help_text) + .color_range(3, 6..=10) + .color_range(3, 15..=25) + } else if menu_item_is_selected { + let help_text = format!("Help: <↓↑> - Navigate, <ENTER> - Learn More, <ESC> - Dismiss"); + Text::new(help_text) + .color_range(1, 6..=9) + .color_range(1, 23..=29) + .color_range(1, 45..=49) + } else { let help_text = format!("Help: <↓↑> - Navigate, <ESC> - Dismiss"); Text::new(help_text) .color_range(1, 6..=9) diff --git a/default-plugins/about/src/tips.rs b/default-plugins/about/src/tips.rs new file mode 100644 index 00000000..3a594e8d --- /dev/null +++ b/default-plugins/about/src/tips.rs @@ -0,0 +1,852 @@ +use zellij_tile::prelude::*; + +use std::cell::RefCell; +use std::rc::Rc; + +use crate::active_component::{ActiveComponent, ClickAction}; +use crate::pages::{BulletinList, ComponentLine, Page, TextOrCustomRender}; + +pub const MAX_TIP_INDEX: usize = 11; + +impl Page { + pub fn new_tip_screen( + link_executable: Rc<RefCell<String>>, + base_mode: Rc<RefCell<InputMode>>, + tip_index: usize, + ) -> Self { + if tip_index == 0 { + Page::tip_1(link_executable) + } else if tip_index == 1 { + Page::tip_2(link_executable, base_mode) + } else if tip_index == 2 { + Page::tip_3(link_executable) + } else if tip_index == 3 { + Page::tip_4(link_executable, base_mode) + } else if tip_index == 4 { + Page::tip_5(link_executable) + } else if tip_index == 5 { + Page::tip_6(link_executable, base_mode) + } else if tip_index == 6 { + Page::tip_7(link_executable) + } else if tip_index == 7 { + Page::tip_8(link_executable) + } else if tip_index == 8 { + Page::tip_9(link_executable) + } else if tip_index == 9 { + Page::tip_10(link_executable, base_mode) + } else if tip_index == 10 { + Page::tip_11(link_executable) + } else if tip_index == 11 { + Page::tip_12(link_executable, base_mode) + } else { + Page::tip_1(link_executable) + } + } + pub fn tip_1(link_executable: Rc<RefCell<String>>) -> Self { + Page::new() + .main_screen() + .with_title(Text::new("Zellij Tip #1").color_range(0, ..)) + .with_paragraph(vec![ + ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("Check out the Zellij screencasts/tutorials to learn how to better take advantage") + )) + ]), + ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("of all the Zellij features. Learn about basic usage, layouts, sessions and more!") + )) + ]) + ]) + .with_paragraph(vec![ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text(Text::new("Follow this link: ").color_range(2, ..))), + ActiveComponent::new(TextOrCustomRender::Text(Text::new("https://zellij.dev/screencasts"))) + .with_hover(TextOrCustomRender::CustomRender( + Box::new(screencasts_link_selected()), + Box::new(screencasts_link_selected_len()), + )) + .with_left_click_action(ClickAction::new_open_link( + format!("https://zellij.dev/screencasts"), + link_executable.clone(), + )), + ])]) + .with_paragraph(vec![ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text(support_the_developer_text())), + ActiveComponent::new(TextOrCustomRender::Text(sponsors_link_text_unselected())) + .with_hover(TextOrCustomRender::CustomRender( + Box::new(sponsors_link_text_selected), + Box::new(sponsors_link_text_selected_len), + )) + .with_left_click_action(ClickAction::new_open_link( + "https://github.com/sponsors/imsnif".to_owned(), + link_executable.clone(), + )), + ])]) + .with_help(Box::new(|hovering_over_link, _menu_item_is_selected| { + tips_help_text(hovering_over_link) + })) + } + pub fn tip_2(link_executable: Rc<RefCell<String>>, base_mode: Rc<RefCell<InputMode>>) -> Self { + Page::new() + .main_screen() + .with_title(Text::new("Zellij Tip #2").color_range(0, ..)) + .with_paragraph(vec![ + ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("You can open the terminal contents in your $EDITOR, allowing you to search") + .color_range(2, 43..=49) + )) + ]), + ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("through them, copy to your clipboard or even save them for later.") + )) + ]) + ]) + .with_paragraph(vec![ComponentLine::new(vec![ + match *base_mode.borrow() { + InputMode::Locked => { + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("While focused on a terminal pane: Ctrl g + s + e") + .color_range(0, 34..=39) + .color_indices(0, vec![43, 47]) + )) + }, + _ => { + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("While focused on a terminal pane: Ctrl s + e") + .color_range(0, 34..=39) + .color_indices(0, vec![43]) + )) + } + } + ])]) + .with_paragraph(vec![ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text(support_the_developer_text())), + ActiveComponent::new(TextOrCustomRender::Text(sponsors_link_text_unselected())) + .with_hover(TextOrCustomRender::CustomRender( + Box::new(sponsors_link_text_selected), + Box::new(sponsors_link_text_selected_len), + )) + .with_left_click_action(ClickAction::new_open_link( + "https://github.com/sponsors/imsnif".to_owned(), + link_executable.clone(), + )), + ])]) + .with_help(Box::new(|hovering_over_link, _menu_item_is_selected| { + tips_help_text(hovering_over_link) + })) + } + pub fn tip_3(link_executable: Rc<RefCell<String>>) -> Self { + Page::new() + .main_screen() + .with_title(Text::new("Zellij Tip #3").color_range(0, ..)) + .with_paragraph(vec![ + ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( + Text::new("Want to make your floating pane bigger?"), + ))]), + ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( + Text::new( + "You can switch to the ENLARGED layout with Alt ] while focused on it.", + ) + .color_range(2, 22..=29) + .color_range(0, 43..=47), + ))]), + ]) + .with_paragraph(vec![ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text(support_the_developer_text())), + ActiveComponent::new(TextOrCustomRender::Text(sponsors_link_text_unselected())) + .with_hover(TextOrCustomRender::CustomRender( + Box::new(sponsors_link_text_selected), + Box::new(sponsors_link_text_selected_len), + )) + .with_left_click_action(ClickAction::new_open_link( + "https://github.com/sponsors/imsnif".to_owned(), + link_executable.clone(), + )), + ])]) + .with_help(Box::new(|hovering_over_link, _menu_item_is_selected| { + tips_help_text(hovering_over_link) + })) + } + fn tip_4(link_executable: Rc<RefCell<String>>, base_mode: Rc<RefCell<InputMode>>) -> Page { + Page::new() + .main_screen() + .with_title(Text::new("Zellij tip #4").color_range(0, ..)) + .with_paragraph(vec![ + ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( + Text::new("It's possible to \"pin\" a floating pane so that it will always"), + ))]), + ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( + Text::new("be visible even if floating panes are hidden."), + ))]), + ]) + .with_bulletin_list( + BulletinList::new( + Text::new(format!("Floating panes can be \"pinned\": ")).color_range(2, ..), + ) + .with_items(vec![ + ActiveComponent::new(TextOrCustomRender::Text( + Text::new(format!("With a mouse click on their top right corner")) + .color_range(3, 7..=17), + )), + ActiveComponent::new(TextOrCustomRender::Text(match *base_mode.borrow() { + InputMode::Locked => Text::new(format!("With Ctrl g + p + i")) + .color_range(3, 5..=10) + .color_range(3, 14..15) + .color_range(3, 18..19), + _ => Text::new("With Ctrl p + i") + .color_range(3, 5..=10) + .color_range(3, 14..15), + })), + ]), + ) + .with_paragraph(vec![ + ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( + Text::new("A great use case for these is to tail log files or to show"), + ))]), + ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( + Text::new(format!( + "real-time compiler output while working in other panes." + )), + ))]), + ]) + .with_paragraph(vec![ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text(support_the_developer_text())), + ActiveComponent::new(TextOrCustomRender::Text(sponsors_link_text_unselected())) + .with_hover(TextOrCustomRender::CustomRender( + Box::new(sponsors_link_text_selected), + Box::new(sponsors_link_text_selected_len), + )) + .with_left_click_action(ClickAction::new_open_link( + "https://github.com/sponsors/imsnif".to_owned(), + link_executable.clone(), + )), + ])]) + .with_help(Box::new(|hovering_over_link, _menu_item_is_selected| { + tips_help_text(hovering_over_link) + })) + } + pub fn tip_5(link_executable: Rc<RefCell<String>>) -> Page { + Page::new() + .main_screen() + .with_title(Text::new("Zellij Tip #5").color_range(0, ..)) + .with_paragraph(vec![ + ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text(Text::new("Panes can be resized into stacks to be managed easier."))), + ]), + ]) + .with_bulletin_list(BulletinList::new(Text::new("To try it out:").color_range(2, ..)) + .with_items(vec![ + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("Hide this pane with Alt f (you can bring it back with Alt f again)") + .color_range(3, 20..=24) + .color_range(3, 54..=58) + )), + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("Open 4-5 panes with Alt n") + .color_range(3, 20..=24) + )), + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("Press Alt + until you reach full screen") + .color_range(3, 6..=10) + )), + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("Press Alt - until you are back at the original state") + .color_range(3, 6..=10) + )), + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("You can always snap back to the built-in swap layouts with Alt <[]>") + .color_range(3, 59..=61) + .color_range(3, 64..=65) + )), + ]) + ) + .with_paragraph(vec![ + ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("To disable this behavior, add stacked_resize false to the Zellij Configuration") + .color_range(3, 30..=49) + )), + ]) + ]) + .with_paragraph(vec![ + ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("For more details, see: ") + .color_range(2, ..) + )), + ActiveComponent::new(TextOrCustomRender::Text(Text::new("https://zellij.dev/screencasts/stacked-resize"))) + .with_hover(TextOrCustomRender::CustomRender(Box::new(stacked_resize_screencast_link_selected), Box::new(stacked_resize_screencast_link_selected_len))) + .with_left_click_action(ClickAction::new_open_link("https://zellij.dev/screencasts/stacked-resize".to_owned(), link_executable.clone())) + ]) + ]) + .with_paragraph(vec![ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text(support_the_developer_text())), + ActiveComponent::new(TextOrCustomRender::Text(sponsors_link_text_unselected())) + .with_hover(TextOrCustomRender::CustomRender( + Box::new(sponsors_link_text_selected), + Box::new(sponsors_link_text_selected_len), + )) + .with_left_click_action(ClickAction::new_open_link( + "https://github.com/sponsors/imsnif".to_owned(), + link_executable.clone(), + )), + ])]) + .with_help(Box::new(|hovering_over_link, _menu_item_is_selected| { + tips_help_text(hovering_over_link) + })) + } + pub fn tip_6(link_executable: Rc<RefCell<String>>, base_mode: Rc<RefCell<InputMode>>) -> Page { + Page::new() + .main_screen() + .with_title(Text::new("Zellij Tip #6").color_range(0, ..)) + .with_paragraph(vec![ + ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text(Text::new("Are the Zellij keybindings colliding with other applications for you?"))) + ]), + ]) + .with_bulletin_list(BulletinList::new(Text::new("Check out the non-colliding keybindings preset:")) + .with_items(vec![ + ActiveComponent::new(TextOrCustomRender::Text( + match *base_mode.borrow() { + InputMode::Locked => { + Text::new("Open the Zellij configuration with Ctrl g + o + c") + .color_range(3, 35..=40) + .color_indices(3, vec![44, 48]) + }, + _ => { + Text::new("Open the Zellij configuration with Ctrl o + c") + .color_range(3, 35..=40) + .color_indices(3, vec![44]) + } + } + )), + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("Press TAB to go to Chagne Mode Behavior") + .color_range(3, 6..=9) + )), + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("Select non-colliding temporarily with ENTER or permanently with Ctrl a") + .color_range(3, 38..=42) + .color_range(3, 64..=69) + )), + ]) + ) + .with_paragraph(vec![ + ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("For more details, see: ") + .color_range(2, ..) + )), + ActiveComponent::new(TextOrCustomRender::Text(Text::new("https://zellij.dev/tutorials/colliding-keybindings"))) + .with_hover(TextOrCustomRender::CustomRender(Box::new(colliding_keybindings_link_selected), Box::new(colliding_keybindings_link_selected_len))) + .with_left_click_action(ClickAction::new_open_link("https://zellij.dev/tutorials/colliding-keybindings".to_owned(), link_executable.clone())) + ]) + ]) + .with_paragraph(vec![ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text(support_the_developer_text())), + ActiveComponent::new(TextOrCustomRender::Text(sponsors_link_text_unselected())) + .with_hover(TextOrCustomRender::CustomRender( + Box::new(sponsors_link_text_selected), + Box::new(sponsors_link_text_selected_len), + )) + .with_left_click_action(ClickAction::new_open_link( + "https://github.com/sponsors/imsnif".to_owned(), + link_executable.clone(), + )), + ])]) + .with_help(Box::new(|hovering_over_link, _menu_item_is_selected| { + tips_help_text(hovering_over_link) + })) + } + pub fn tip_7(link_executable: Rc<RefCell<String>>) -> Page { + Page::new() + .main_screen() + .with_title(Text::new("Zellij Tip #7").color_range(0, ..)) + .with_paragraph(vec![ComponentLine::new(vec![ActiveComponent::new( + TextOrCustomRender::Text(Text::new( + "Want to customize the appearance and colors of Zellij?", + )), + )])]) + .with_paragraph(vec![ + ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("Check out the built-in themes: ").color_range(2, ..), + )), + ActiveComponent::new(TextOrCustomRender::Text(Text::new( + "https://zellij.dev/documentation/theme-list", + ))) + .with_hover(TextOrCustomRender::CustomRender( + Box::new(theme_list_selected), + Box::new(theme_list_selected_len), + )) + .with_left_click_action(ClickAction::new_open_link( + "https://zellij.dev/documentation/theme-list".to_owned(), + link_executable.clone(), + )), + ]), + ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("Or create your own theme: ").color_range(2, ..), + )), + ActiveComponent::new(TextOrCustomRender::Text(Text::new( + "https://zellij.dev/documentation/themes", + ))) + .with_hover(TextOrCustomRender::CustomRender( + Box::new(theme_link_selected), + Box::new(theme_link_selected_len), + )) + .with_left_click_action(ClickAction::new_open_link( + "https://zellij.dev/documentation/themes".to_owned(), + link_executable.clone(), + )), + ]), + ]) + .with_paragraph(vec![ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text(support_the_developer_text())), + ActiveComponent::new(TextOrCustomRender::Text(sponsors_link_text_unselected())) + .with_hover(TextOrCustomRender::CustomRender( + Box::new(sponsors_link_text_selected), + Box::new(sponsors_link_text_selected_len), + )) + .with_left_click_action(ClickAction::new_open_link( + "https://github.com/sponsors/imsnif".to_owned(), + link_executable.clone(), + )), + ])]) + .with_help(Box::new(|hovering_over_link, _menu_item_is_selected| { + tips_help_text(hovering_over_link) + })) + } + pub fn tip_8(link_executable: Rc<RefCell<String>>) -> Page { + Page::new() + .main_screen() + .with_title(Text::new("Zellij Tip #8").color_range(0, ..)) + .with_paragraph(vec![ + ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("If you change the pane focus with Alt + <←↓↑→> or Alt + <hjkl> beyond the") + .color_range(0, 34..=36) + .color_range(2, 40..=45) + .color_range(0, 50..=52) + .color_range(2, 56..=60) + )) + ]), + ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text(Text::new("right or left side of the screen, the next or previous tab will be focused."))) + ]), + ]) + .with_paragraph(vec![ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text(support_the_developer_text())), + ActiveComponent::new(TextOrCustomRender::Text(sponsors_link_text_unselected())) + .with_hover(TextOrCustomRender::CustomRender( + Box::new(sponsors_link_text_selected), + Box::new(sponsors_link_text_selected_len), + )) + .with_left_click_action(ClickAction::new_open_link( + "https://github.com/sponsors/imsnif".to_owned(), + link_executable.clone(), + )), + ])]) + .with_help(Box::new(|hovering_over_link, _menu_item_is_selected| { + tips_help_text(hovering_over_link) + })) + } + pub fn tip_9(link_executable: Rc<RefCell<String>>) -> Page { + Page::new() + .main_screen() + .with_title(Text::new("Zellij Tip #9").color_range(0, ..)) + .with_paragraph(vec![ + ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("For plugins, integrations and tutorials created by the community, check out the") + )) + ]), + ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("Awesome-zellij repository: ") + .color_range(2, ..=39) + )), + ActiveComponent::new(TextOrCustomRender::Text(Text::new("https://github.com/zellij-org/awesome-zellij"))) + .with_hover(TextOrCustomRender::CustomRender( + Box::new(awesome_zellij_link_text_selected), + Box::new(awesome_zellij_link_text_selected_len), + )) + .with_left_click_action(ClickAction::new_open_link( + "https://github.com/zellij-org/awesome-zellij".to_owned(), + link_executable.clone(), + )), + ]), + ]) + .with_paragraph(vec![ + ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("For community and support:") + .color_range(2, ..) + )) + ]), + ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text(Text::new("Discord: "))), + ActiveComponent::new(TextOrCustomRender::Text(Text::new("https://discord.com/invite/CrUAFH3"))) + .with_hover(TextOrCustomRender::CustomRender( + Box::new(discord_link_text_selected), + Box::new(discord_link_text_selected_len), + )) + .with_left_click_action(ClickAction::new_open_link( + "https://discord.com/invite/CrUAFH3".to_owned(), + link_executable.clone(), + )), + ]), + ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text(Text::new("Matrix: "))), + ActiveComponent::new(TextOrCustomRender::Text(Text::new("https://matrix.to/#/#zellij_general:matrix.org"))) + .with_hover(TextOrCustomRender::CustomRender( + Box::new(matrix_link_text_selected), + Box::new(matrix_link_text_selected_len), + )) + .with_left_click_action(ClickAction::new_open_link( + "https://matrix.to/#/#zellij_general:matrix.org".to_owned(), + link_executable.clone(), + )), + ]) + ]) + .with_paragraph(vec![ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text(support_the_developer_text())), + ActiveComponent::new(TextOrCustomRender::Text(sponsors_link_text_unselected())) + .with_hover(TextOrCustomRender::CustomRender( + Box::new(sponsors_link_text_selected), + Box::new(sponsors_link_text_selected_len), + )) + .with_left_click_action(ClickAction::new_open_link( + "https://github.com/sponsors/imsnif".to_owned(), + link_executable.clone(), + )), + ])]) + .with_help(Box::new(|hovering_over_link, _menu_item_is_selected| { + tips_help_text(hovering_over_link) + })) + } + pub fn tip_10(link_executable: Rc<RefCell<String>>, base_mode: Rc<RefCell<InputMode>>) -> Page { + Page::new() + .main_screen() + .with_title(Text::new("Zellij Tip #10").color_range(0, ..)) + .with_bulletin_list( + BulletinList::new( + Text::new("The Zellij session-manager can:").color_range(2, 11..=25), + ) + .with_items(vec![ + ActiveComponent::new(TextOrCustomRender::Text(Text::new( + "Create new sessions", + ))), + ActiveComponent::new(TextOrCustomRender::Text(Text::new( + "Switch between existing sessions", + ))), + ActiveComponent::new(TextOrCustomRender::Text(Text::new( + "Resurrect exited sessions", + ))), + ActiveComponent::new(TextOrCustomRender::Text(Text::new( + "Change the session name", + ))), + ActiveComponent::new(TextOrCustomRender::Text(Text::new( + "Disconnect other users from the current session", + ))), + ]), + ) + .with_paragraph(vec![ComponentLine::new(vec![ActiveComponent::new( + TextOrCustomRender::Text(match *base_mode.borrow() { + InputMode::Locked => Text::new("Check it out with with: Ctrl g + o + w") + .color_range(3, 24..=29) + .color_indices(3, vec![33, 37]), + _ => Text::new("Check it out with with: Ctrl o + w") + .color_range(3, 24..=29) + .color_indices(3, vec![33]), + }), + )])]) + .with_paragraph(vec![ComponentLine::new(vec![ActiveComponent::new( + TextOrCustomRender::Text( + Text::new("You can also use it as a welcome screen with: zellij -l welcome") + .color_range(0, 46..=62), + ), + )])]) + .with_paragraph(vec![ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text(support_the_developer_text())), + ActiveComponent::new(TextOrCustomRender::Text(sponsors_link_text_unselected())) + .with_hover(TextOrCustomRender::CustomRender( + Box::new(sponsors_link_text_selected), + Box::new(sponsors_link_text_selected_len), + )) + .with_left_click_action(ClickAction::new_open_link( + "https://github.com/sponsors/imsnif".to_owned(), + link_executable.clone(), + )), + ])]) + .with_help(Box::new(|hovering_over_link, _menu_item_is_selected| { + tips_help_text(hovering_over_link) + })) + } + pub fn tip_11(link_executable: Rc<RefCell<String>>) -> Page { + Page::new() + .main_screen() + .with_title(Text::new("Zellij Tip #11").color_range(0, ..)) + .with_paragraph(vec![ + ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("You can change the arrangement of panes on screen with Alt + []") + .color_range(0, 55..=57) + .color_range(2, 61..=62) + )), + ]), + ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("This works with tiled or floating panes, depending which is visible.") + )) + ]) + ]) + .with_paragraph(vec![ + ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("Resizing or splitting a pane breaks out of this arrangement. It is then possible") + )), + ]), + ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("to snap back by pressing Alt + [] once more. This status can be seen") + .color_range(0, 25..=27) + .color_range(2, 31..=32) + )), + ]), + ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("on the top right corner of the screen.") + )), + ]), + ]) + .with_paragraph(vec![ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text(support_the_developer_text())), + ActiveComponent::new(TextOrCustomRender::Text(sponsors_link_text_unselected())) + .with_hover(TextOrCustomRender::CustomRender( + Box::new(sponsors_link_text_selected), + Box::new(sponsors_link_text_selected_len), + )) + .with_left_click_action(ClickAction::new_open_link( + "https://github.com/sponsors/imsnif".to_owned(), + link_executable.clone(), + )), + ])]) + .with_help(Box::new(|hovering_over_link, _menu_item_is_selected| { + tips_help_text(hovering_over_link) + })) + } + pub fn tip_12(link_executable: Rc<RefCell<String>>, base_mode: Rc<RefCell<InputMode>>) -> Page { + Page::new() + .main_screen() + .with_title(Text::new("Zellij Tip #12").color_range(0, ..)) + .with_paragraph(vec![ + ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("Zellij plugins can be loaded, reloaded and tracked from the plugin-manager.") + )), + ]), + ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text( + match *base_mode.borrow() { + InputMode::Locked => { + Text::new("Check it out with with: Ctrl g + o + p") + .color_range(3, 24..=29) + .color_indices(3, vec![33, 37]) + }, + _ => { + Text::new("Check it out with with: Ctrl o + p") + .color_range(3, 24..=29) + .color_indices(3, vec![33]) + } + } + )), + ]), + ]) + .with_paragraph(vec![ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text(Text::new("To learn more about plugins: ").color_range(2, ..))), + ActiveComponent::new(TextOrCustomRender::Text(Text::new("https://zellij.dev/documentation/plugins"))) + .with_hover(TextOrCustomRender::CustomRender( + Box::new(plugin_docs_link_text_selected), + Box::new(plugin_docs_link_text_selected_len), + )) + .with_left_click_action(ClickAction::new_open_link( + "https://zellij.dev/documentation/plugins".to_owned(), + link_executable.clone(), + )), + ])]) + .with_paragraph(vec![ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text(support_the_developer_text())), + ActiveComponent::new(TextOrCustomRender::Text(sponsors_link_text_unselected())) + .with_hover(TextOrCustomRender::CustomRender( + Box::new(sponsors_link_text_selected), + Box::new(sponsors_link_text_selected_len), + )) + .with_left_click_action(ClickAction::new_open_link( + "https://github.com/sponsors/imsnif".to_owned(), + link_executable.clone(), + )), + ])]) + .with_help(Box::new(|hovering_over_link, _menu_item_is_selected| { + tips_help_text(hovering_over_link) + })) + } +} + +fn sponsors_link_text_unselected() -> Text { + Text::new("https://github.com/sponsors/imsnif") +} + +fn sponsors_link_text_selected(x: usize, y: usize) -> usize { + print!( + "\u{1b}[{};{}H\u{1b}[m\u{1b}[1;4mhttps://github.com/sponsors/imsnif", + y + 1, + x + 1 + ); + 34 +} + +fn sponsors_link_text_selected_len() -> usize { + 34 +} + +fn plugin_docs_link_text_selected(x: usize, y: usize) -> usize { + print!( + "\u{1b}[{};{}H\u{1b}[m\u{1b}[1;4mhttps://zellij.dev/documentation/plugins", + y + 1, + x + 1 + ); + 40 +} + +fn plugin_docs_link_text_selected_len() -> usize { + 40 +} + +fn awesome_zellij_link_text_selected(x: usize, y: usize) -> usize { + print!( + "\u{1b}[{};{}H\u{1b}[m\u{1b}[1;4mhttps://github.com/zellij-org/awesome-zellij", + y + 1, + x + 1 + ); + 44 +} + +fn awesome_zellij_link_text_selected_len() -> usize { + 44 +} + +fn discord_link_text_selected(x: usize, y: usize) -> usize { + print!( + "\u{1b}[{};{}H\u{1b}[m\u{1b}[1;4mhttps://discord.com/invite/CrUAFH3", + y + 1, + x + 1 + ); + 34 +} + +fn discord_link_text_selected_len() -> usize { + 34 +} + +fn matrix_link_text_selected(x: usize, y: usize) -> usize { + print!( + "\u{1b}[{};{}H\u{1b}[m\u{1b}[1;4mhttps://matrix.to/#/#zellij_general:matrix.org", + y + 1, + x + 1 + ); + 46 +} + +fn matrix_link_text_selected_len() -> usize { + 46 +} + +fn stacked_resize_screencast_link_selected(x: usize, y: usize) -> usize { + print!( + "\u{1b}[{};{}H\u{1b}[m\u{1b}[1;4mhttps://zellij.dev/screencasts/stacked-resize", + y + 1, + x + 1 + ); + 45 +} + +fn stacked_resize_screencast_link_selected_len() -> usize { + 45 +} + +fn colliding_keybindings_link_selected(x: usize, y: usize) -> usize { + print!( + "\u{1b}[{};{}H\u{1b}[m\u{1b}[1;4mhttps://zellij.dev/tutorials/colliding-keybindings", + y + 1, + x + 1 + ); + 51 +} + +fn colliding_keybindings_link_selected_len() -> usize { + 51 +} + +fn theme_link_selected(x: usize, y: usize) -> usize { + print!( + "\u{1b}[{};{}H\u{1b}[m\u{1b}[1;4mhttps://zellij.dev/documentation/themes", + y + 1, + x + 1 + ); + 39 +} +fn theme_link_selected_len() -> usize { + 39 +} + +fn theme_list_selected(x: usize, y: usize) -> usize { + print!( + "\u{1b}[{};{}H\u{1b}[m\u{1b}[1;4mhttps://zellij.dev/documentation/theme-list", + y + 1, + x + 1 + ); + 43 +} +fn theme_list_selected_len() -> usize { + 43 +} + +fn support_the_developer_text() -> Text { + let support_text = format!("Please support the Zellij developer <3: "); + Text::new(support_text).color_range(3, ..) +} + +fn screencasts_link_selected() -> Box<dyn Fn(usize, usize) -> usize> { + Box::new(move |x, y| { + print!( + "\u{1b}[{};{}H\u{1b}[m\u{1b}[1;4mhttps://zellij.dev/screencasts", + y + 1, + x + 1, + ); + 30 + }) +} + +fn screencasts_link_selected_len() -> Box<dyn Fn() -> usize> { + Box::new(move || 30) +} + +fn tips_help_text(hovering_over_link: bool) -> Text { + if hovering_over_link { + let help_text = format!("Help: Click or Shift-Click to open in browser"); + Text::new(help_text) + .color_range(3, 6..=10) + .color_range(3, 15..=25) + } else { + let help_text = format!( + "Help: <ESC> - Dismiss, <↓↑> - Browse tips, <Ctrl c> - Don't show tips on startup" + ); + Text::new(help_text) + .color_range(1, 6..=10) + .color_range(1, 23..=26) + .color_range(1, 43..=50) + } +} diff --git a/default-plugins/configuration/src/presets.rs b/default-plugins/configuration/src/presets.rs index eb9a55bb..41d7a79e 100644 --- a/default-plugins/configuration/src/presets.rs +++ b/default-plugins/configuration/src/presets.rs @@ -140,6 +140,13 @@ keybinds clear-defaults=true {{ }}; SwitchToMode "Locked" }} + bind "a" {{ + LaunchOrFocusPlugin "zellij:about" {{ + floating true + move_to_focused_tab true + }}; + SwitchToMode "Locked" + }} }} shared_except "locked" "renametab" "renamepane" {{ bind "{primary_modifier} g" {{ SwitchToMode "Locked"; }} @@ -339,6 +346,13 @@ keybinds clear-defaults=true {{ }}; SwitchToMode "Normal" }} + bind "a" {{ + LaunchOrFocusPlugin "zellij:about" {{ + floating true + move_to_focused_tab true + }}; + SwitchToMode "Normal" + }} }} tmux {{ bind "[" {{ SwitchToMode "Scroll"; }} @@ -538,6 +552,13 @@ keybinds clear-defaults=true {{ }}; SwitchToMode "Normal" }} + bind "a" {{ + LaunchOrFocusPlugin "zellij:about" {{ + floating true + move_to_focused_tab true + }}; + SwitchToMode "Normal" + }} }} tmux {{ bind "[" {{ SwitchToMode "Scroll"; }} @@ -718,6 +739,13 @@ keybinds clear-defaults=true {{ }}; SwitchToMode "Normal" }} + bind "a" {{ + LaunchOrFocusPlugin "zellij:about" {{ + floating true + move_to_focused_tab true + }}; + SwitchToMode "Normal" + }} }} tmux {{ bind "[" {{ SwitchToMode "Scroll"; }} @@ -900,6 +928,13 @@ keybinds clear-defaults=true {{ }}; SwitchToMode "Normal" }} + bind "a" {{ + LaunchOrFocusPlugin "zellij:about" {{ + floating true + move_to_focused_tab true + }}; + SwitchToMode "Normal" + }} }} tmux {{ bind "[" {{ SwitchToMode "Scroll"; }} @@ -1077,6 +1112,13 @@ keybinds clear-defaults=true {{ }}; SwitchToMode "Normal" }} + bind "a" {{ + LaunchOrFocusPlugin "zellij:about" {{ + floating true + move_to_focused_tab true + }}; + SwitchToMode "Normal" + }} }} tmux {{ bind "[" {{ SwitchToMode "Scroll"; }} diff --git a/default-plugins/status-bar/src/one_line_ui.rs b/default-plugins/status-bar/src/one_line_ui.rs index 4d47281e..3b7eec10 100644 --- a/default-plugins/status-bar/src/one_line_ui.rs +++ b/default-plugins/status-bar/src/one_line_ui.rs @@ -1276,6 +1276,7 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec<KeyWithModifier (s("Session Manager"), s("Manager"), session_manager_key(&km)), (s("Configure"), s("Config"), configuration_key(&km)), (s("Plugin Manager"), s("Plugins"), plugin_manager_key(&km)), + (s("About"), s("About"), about_key(&km)), (s("Select pane"), s("Select"), to_basemode_key), ]} else if mi.mode == IM::Tmux { vec![ (s("Move focus"), s("Move"), action_key_group(&km, &[ @@ -1385,6 +1386,25 @@ fn plugin_manager_key(keymap: &[(KeyWithModifier, Vec<Action>)]) -> Vec<KeyWithM } } +fn about_key(keymap: &[(KeyWithModifier, Vec<Action>)]) -> Vec<KeyWithModifier> { + let mut matching = keymap.iter().find_map(|(key, acvec)| { + let has_match = acvec + .iter() + .find(|a| a.launches_plugin("zellij:about")) + .is_some(); + if has_match { + Some(key.clone()) + } else { + None + } + }); + if let Some(matching) = matching.take() { + vec![matching] + } else { + vec![] + } +} + fn configuration_key(keymap: &[(KeyWithModifier, Vec<Action>)]) -> Vec<KeyWithModifier> { let mut matching = keymap.iter().find_map(|(key, acvec)| { let has_match = acvec |
