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
This commit is contained in:
Aram Drevekenin 2025-02-13 17:12:47 +01:00 committed by GitHub
parent e11daf248f
commit 7aaa7aea8f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 1563 additions and 58 deletions

122
Cargo.lock generated
View file

@ -6,8 +6,7 @@ version = 4
name = "about" name = "about"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"ansi_term", "rand 0.9.0",
"chrono",
"zellij-tile", "zellij-tile",
] ]
@ -44,7 +43,7 @@ dependencies = [
"cfg-if", "cfg-if",
"once_cell", "once_cell",
"version_check", "version_check",
"zerocopy", "zerocopy 0.7.34",
] ]
[[package]] [[package]]
@ -458,7 +457,7 @@ dependencies = [
"cap-primitives", "cap-primitives",
"cap-std", "cap-std",
"io-lifetimes 2.0.3", "io-lifetimes 2.0.3",
"windows-sys 0.52.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@ -486,7 +485,7 @@ dependencies = [
"ipnet", "ipnet",
"maybe-owned", "maybe-owned",
"rustix 0.38.44", "rustix 0.38.44",
"windows-sys 0.52.0", "windows-sys 0.59.0",
"winx", "winx",
] ]
@ -497,7 +496,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dea13372b49df066d1ae654e5c6e41799c1efd9f6b36794b921e877ea4037977" checksum = "dea13372b49df066d1ae654e5c6e41799c1efd9f6b36794b921e877ea4037977"
dependencies = [ dependencies = [
"ambient-authority", "ambient-authority",
"rand", "rand 0.8.5",
] ]
[[package]] [[package]]
@ -1165,7 +1164,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.52.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@ -1509,6 +1508,18 @@ dependencies = [
"wasi 0.11.0+wasi-snapshot-preview1", "wasi 0.11.0+wasi-snapshot-preview1",
] ]
[[package]]
name = "getrandom"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
dependencies = [
"cfg-if",
"libc",
"wasi 0.13.3+wasi-0.2.2",
"windows-targets 0.52.6",
]
[[package]] [[package]]
name = "ghost" name = "ghost"
version = "0.1.4" version = "0.1.4"
@ -1834,7 +1845,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65" checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65"
dependencies = [ dependencies = [
"io-lifetimes 2.0.3", "io-lifetimes 2.0.3",
"windows-sys 0.52.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@ -2328,7 +2339,7 @@ version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bddcd3bf5144b6392de80e04c347cd7fab2508f6df16a85fc496ecd5cec39bc" checksum = "7bddcd3bf5144b6392de80e04c347cd7fab2508f6df16a85fc496ecd5cec39bc"
dependencies = [ dependencies = [
"rand", "rand 0.8.5",
] ]
[[package]] [[package]]
@ -2712,7 +2723,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf" checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf"
dependencies = [ dependencies = [
"phf_shared", "phf_shared",
"rand", "rand 0.8.5",
] ]
[[package]] [[package]]
@ -2958,8 +2969,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [ dependencies = [
"libc", "libc",
"rand_chacha", "rand_chacha 0.3.1",
"rand_core", "rand_core 0.6.3",
]
[[package]]
name = "rand"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.0",
"zerocopy 0.8.17",
] ]
[[package]] [[package]]
@ -2969,7 +2991,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [ dependencies = [
"ppv-lite86", "ppv-lite86",
"rand_core", "rand_core 0.6.3",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core 0.9.0",
] ]
[[package]] [[package]]
@ -2978,7 +3010,17 @@ version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [ dependencies = [
"getrandom", "getrandom 0.2.10",
]
[[package]]
name = "rand_core"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff"
dependencies = [
"getrandom 0.3.1",
"zerocopy 0.8.17",
] ]
[[package]] [[package]]
@ -3029,7 +3071,7 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [ dependencies = [
"getrandom", "getrandom 0.2.10",
"redox_syscall 0.2.13", "redox_syscall 0.2.13",
"thiserror", "thiserror",
] ]
@ -3134,7 +3176,7 @@ dependencies = [
"libc", "libc",
"linux-raw-sys 0.4.15", "linux-raw-sys 0.4.15",
"once_cell", "once_cell",
"windows-sys 0.52.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@ -3458,7 +3500,7 @@ dependencies = [
"ansi_term", "ansi_term",
"colored", "colored",
"lazy_static", "lazy_static",
"rand", "rand 0.8.5",
"regex", "regex",
"serde", "serde",
"serde_json", "serde_json",
@ -4052,7 +4094,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
dependencies = [ dependencies = [
"atomic", "atomic",
"getrandom", "getrandom 0.2.10",
"serde", "serde",
] ]
@ -4146,6 +4188,15 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasi"
version = "0.13.3+wasi-0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
dependencies = [
"wit-bindgen-rt",
]
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.87" version = "0.2.87"
@ -4617,7 +4668,7 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5a5e0adf7eed68976410def849a4bdab6f6e9f6163f152de9cb89deea9e60b" checksum = "8e5a5e0adf7eed68976410def849a4bdab6f6e9f6163f152de9cb89deea9e60b"
dependencies = [ dependencies = [
"getrandom", "getrandom 0.2.10",
"mac_address", "mac_address",
"once_cell", "once_cell",
"sha2", "sha2",
@ -5030,6 +5081,15 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "wit-bindgen-rt"
version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
dependencies = [
"bitflags 2.5.0",
]
[[package]] [[package]]
name = "wit-parser" name = "wit-parser"
version = "0.221.2" version = "0.221.2"
@ -5120,7 +5180,7 @@ dependencies = [
"insta", "insta",
"log", "log",
"names", "names",
"rand", "rand 0.8.5",
"regex", "regex",
"ssh2", "ssh2",
"suggest", "suggest",
@ -5255,7 +5315,16 @@ version = "0.7.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087"
dependencies = [ dependencies = [
"zerocopy-derive", "zerocopy-derive 0.7.34",
]
[[package]]
name = "zerocopy"
version = "0.8.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa91407dacce3a68c56de03abe2760159582b846c6a4acd2f456618087f12713"
dependencies = [
"zerocopy-derive 0.8.17",
] ]
[[package]] [[package]]
@ -5269,6 +5338,17 @@ dependencies = [
"syn 2.0.96", "syn 2.0.96",
] ]
[[package]]
name = "zerocopy-derive"
version = "0.8.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06718a168365cad3d5ff0bb133aad346959a2074bd4a85c121255a11304a8626"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.96",
]
[[package]] [[package]]
name = "zeroize" name = "zeroize"
version = "1.5.5" version = "1.5.5"

View file

@ -6,6 +6,5 @@ edition = "2021"
license = "MIT" license = "MIT"
[dependencies] [dependencies]
ansi_term = "0.12.1"
zellij-tile = { path = "../../zellij-tile" } zellij-tile = { path = "../../zellij-tile" }
chrono = "0.4.0" rand = "0.9.0"

View file

@ -1,11 +1,15 @@
mod active_component; mod active_component;
mod pages; mod pages;
mod tips;
use zellij_tile::prelude::*; use zellij_tile::prelude::*;
use pages::Page; use pages::Page;
use rand::prelude::*;
use rand::rng;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::rc::Rc; use std::rc::Rc;
use tips::MAX_TIP_INDEX;
const UI_ROWS: usize = 20; const UI_ROWS: usize = 20;
const UI_COLUMNS: usize = 90; const UI_COLUMNS: usize = 90;
@ -20,6 +24,10 @@ struct App {
tab_columns: usize, tab_columns: usize,
own_plugin_id: Option<u32>, own_plugin_id: Option<u32>,
is_release_notes: bool, 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 { impl Default for App {
@ -32,6 +40,7 @@ impl Default for App {
link_executable.clone(), link_executable.clone(),
"".to_owned(), "".to_owned(),
base_mode.clone(), base_mode.clone(),
false,
), ),
link_executable, link_executable,
zellij_version, zellij_version,
@ -40,6 +49,10 @@ impl Default for App {
tab_columns: 0, tab_columns: 0,
own_plugin_id: None, own_plugin_id: None,
is_release_notes: false, 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") .get("is_release_notes")
.map(|v| v == "true") .map(|v| v == "true")
.unwrap_or(false); .unwrap_or(false);
self.is_startup_tip = configuration
.get("is_startup_tip")
.map(|v| v == "true")
.unwrap_or(false);
subscribe(&[ subscribe(&[
EventType::Key, EventType::Key,
EventType::Mouse, EventType::Mouse,
EventType::ModeUpdate, EventType::ModeUpdate,
EventType::RunCommandResult, EventType::RunCommandResult,
EventType::TabUpdate, EventType::TabUpdate,
EventType::FailedToWriteConfigToDisk,
EventType::ConfigWasWrittenToDisk,
]); ]);
let own_plugin_id = get_plugin_ids().plugin_id; let own_plugin_id = get_plugin_ids().plugin_id;
self.own_plugin_id = Some(own_plugin_id); self.own_plugin_id = Some(own_plugin_id);
*self.zellij_version.borrow_mut() = get_zellij_version(); *self.zellij_version.borrow_mut() = get_zellij_version();
self.change_own_title(); self.change_own_title();
self.query_link_executable(); self.query_link_executable();
self.active_page = Page::new_main_screen( self.active_page = if self.is_startup_tip {
self.link_executable.clone(), let mut rng = rng();
self.zellij_version.borrow().clone(), self.tip_index = rng.random_range(0..=MAX_TIP_INDEX);
self.base_mode.clone(), 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 { fn update(&mut self, event: Event) -> bool {
let mut should_render = false; let mut should_render = false;
match event { 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) => { Event::TabUpdate(tab_info) => {
self.center_own_pane(tab_info); self.center_own_pane(tab_info);
}, },
@ -98,14 +146,19 @@ impl ZellijPlugin for App {
} }
}, },
Event::Key(key) => { 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 should_render
} }
fn render(&mut self, rows: usize, cols: usize) { 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 { pub fn handle_key(&mut self, key: KeyWithModifier) -> bool {
let mut should_render = false; 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() { if let Some(new_page) = self.active_page.handle_selection() {
self.active_page = new_page; self.active_page = new_page;
should_render = true; 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() { } else if key.bare_key == BareKey::Esc && key.has_no_modifiers() {
if self.active_page.is_main_screen { if self.active_page.is_main_screen {
close_self(); close_self();
@ -175,9 +241,21 @@ impl App {
self.link_executable.clone(), self.link_executable.clone(),
self.zellij_version.borrow().clone(), self.zellij_version.borrow().clone(),
self.base_mode.clone(), self.base_mode.clone(),
self.is_release_notes,
); );
should_render = true; 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 { } else {
should_render = self.active_page.handle_key(key); 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,
);
}
} }

View file

@ -20,10 +20,11 @@ impl Page {
link_executable: Rc<RefCell<String>>, link_executable: Rc<RefCell<String>>,
zellij_version: String, zellij_version: String,
base_mode: Rc<RefCell<InputMode>>, base_mode: Rc<RefCell<InputMode>>,
is_release_notes: bool,
) -> Self { ) -> Self {
Page::new() Page::new()
.main_screen() .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![ .with_bulletin_list(BulletinList::new(whats_new_title()).with_items(vec![
ActiveComponent::new(TextOrCustomRender::Text(main_menu_item( ActiveComponent::new(TextOrCustomRender::Text(main_menu_item(
"Stacked Resize", "Stacked Resize",
@ -102,9 +103,15 @@ impl Page {
link_executable.clone(), link_executable.clone(),
)), )),
])]) ])])
.with_help(Box::new(|hovering_over_link, menu_item_is_selected| { .with_help(if is_release_notes {
main_screen_help_text(hovering_over_link, menu_item_is_selected) 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 { pub fn new_stacked_resize(link_executable: Rc<RefCell<String>>) -> Page {
Page::new() Page::new()
@ -543,7 +550,7 @@ impl Page {
row_count += self.components_to_render.len(); row_count += self.components_to_render.len();
row_count 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_x = columns.saturating_sub(self.ui_column_count()) / 2;
let base_y = rows.saturating_sub(self.ui_row_count()) / 2; let base_y = rows.saturating_sub(self.ui_row_count()) / 2;
let mut current_y = base_y; let mut current_y = base_y;
@ -562,12 +569,23 @@ impl Page {
RenderedComponent::HelpText(_) => true, RenderedComponent::HelpText(_) => true,
_ => false, _ => false,
}; };
if is_help {
if let Some(error) = error {
render_error(error, rows);
continue;
}
}
let y = if is_help { rows } else { current_y }; 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( let rendered_rows = rendered_component.render(
base_x, base_x,
y, y,
rows, rows,
columns.saturating_sub(base_x * 2), columns,
self.hovering_over_link, self.hovering_over_link,
self.menu_item_is_selected, 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 { fn changelog_link_unselected(version: String) -> Text {
let full_changelog_text = format!( let full_changelog_text = format!(
"https://github.com/zellij-org/zellij/releases/tag/v{}", "https://github.com/zellij-org/zellij/releases/tag/v{}",
@ -647,12 +675,38 @@ fn whats_new_title() -> Text {
Text::new("What's new?") Text::new("What's new?")
} }
fn main_screen_title(version: String) -> Text { fn main_screen_title(version: String, is_release_notes: bool) -> Text {
let title_text = format!("Hi there, welcome to Zellij {}!", &version); if is_release_notes {
Text::new(title_text).color_range(2, 21..=27 + version.chars().count()) 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 { fn main_screen_help_text(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, <?> - 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 { if hovering_over_link {
let help_text = format!("Help: Click or Shift-Click to open in browser"); let help_text = format!("Help: Click or Shift-Click to open in browser");
Text::new(help_text) Text::new(help_text)

View file

@ -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)
}
}

View file

@ -140,6 +140,13 @@ keybinds clear-defaults=true {{
}}; }};
SwitchToMode "Locked" SwitchToMode "Locked"
}} }}
bind "a" {{
LaunchOrFocusPlugin "zellij:about" {{
floating true
move_to_focused_tab true
}};
SwitchToMode "Locked"
}}
}} }}
shared_except "locked" "renametab" "renamepane" {{ shared_except "locked" "renametab" "renamepane" {{
bind "{primary_modifier} g" {{ SwitchToMode "Locked"; }} bind "{primary_modifier} g" {{ SwitchToMode "Locked"; }}
@ -339,6 +346,13 @@ keybinds clear-defaults=true {{
}}; }};
SwitchToMode "Normal" SwitchToMode "Normal"
}} }}
bind "a" {{
LaunchOrFocusPlugin "zellij:about" {{
floating true
move_to_focused_tab true
}};
SwitchToMode "Normal"
}}
}} }}
tmux {{ tmux {{
bind "[" {{ SwitchToMode "Scroll"; }} bind "[" {{ SwitchToMode "Scroll"; }}
@ -538,6 +552,13 @@ keybinds clear-defaults=true {{
}}; }};
SwitchToMode "Normal" SwitchToMode "Normal"
}} }}
bind "a" {{
LaunchOrFocusPlugin "zellij:about" {{
floating true
move_to_focused_tab true
}};
SwitchToMode "Normal"
}}
}} }}
tmux {{ tmux {{
bind "[" {{ SwitchToMode "Scroll"; }} bind "[" {{ SwitchToMode "Scroll"; }}
@ -718,6 +739,13 @@ keybinds clear-defaults=true {{
}}; }};
SwitchToMode "Normal" SwitchToMode "Normal"
}} }}
bind "a" {{
LaunchOrFocusPlugin "zellij:about" {{
floating true
move_to_focused_tab true
}};
SwitchToMode "Normal"
}}
}} }}
tmux {{ tmux {{
bind "[" {{ SwitchToMode "Scroll"; }} bind "[" {{ SwitchToMode "Scroll"; }}
@ -900,6 +928,13 @@ keybinds clear-defaults=true {{
}}; }};
SwitchToMode "Normal" SwitchToMode "Normal"
}} }}
bind "a" {{
LaunchOrFocusPlugin "zellij:about" {{
floating true
move_to_focused_tab true
}};
SwitchToMode "Normal"
}}
}} }}
tmux {{ tmux {{
bind "[" {{ SwitchToMode "Scroll"; }} bind "[" {{ SwitchToMode "Scroll"; }}
@ -1077,6 +1112,13 @@ keybinds clear-defaults=true {{
}}; }};
SwitchToMode "Normal" SwitchToMode "Normal"
}} }}
bind "a" {{
LaunchOrFocusPlugin "zellij:about" {{
floating true
move_to_focused_tab true
}};
SwitchToMode "Normal"
}}
}} }}
tmux {{ tmux {{
bind "[" {{ SwitchToMode "Scroll"; }} bind "[" {{ SwitchToMode "Scroll"; }}

View file

@ -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("Session Manager"), s("Manager"), session_manager_key(&km)),
(s("Configure"), s("Config"), configuration_key(&km)), (s("Configure"), s("Config"), configuration_key(&km)),
(s("Plugin Manager"), s("Plugins"), plugin_manager_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), (s("Select pane"), s("Select"), to_basemode_key),
]} else if mi.mode == IM::Tmux { vec![ ]} else if mi.mode == IM::Tmux { vec![
(s("Move focus"), s("Move"), action_key_group(&km, &[ (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> { fn configuration_key(keymap: &[(KeyWithModifier, Vec<Action>)]) -> Vec<KeyWithModifier> {
let mut matching = keymap.iter().find_map(|(key, acvec)| { let mut matching = keymap.iter().find_map(|(key, acvec)| {
let has_match = acvec let has_match = acvec

View file

@ -135,6 +135,13 @@ keybinds {
}; };
SwitchToMode "Normal" SwitchToMode "Normal"
} }
bind "a" {
LaunchOrFocusPlugin "zellij:about" {
floating true
move_to_focused_tab true
};
SwitchToMode "Normal"
}
} }
tmux { tmux {
bind "[" { SwitchToMode "Scroll"; } bind "[" { SwitchToMode "Scroll"; }
@ -413,3 +420,13 @@ load_plugins {
// Default: true // Default: true
// //
// stacked_resize false // stacked_resize false
// Whether to show tips on startup
// Default: true
//
// show_tips_on_startup false
// Whether to show release notes on first version run
// Default: true
//
// show_release_notes false

View file

@ -81,7 +81,7 @@ fn start_zellij(channel: &mut ssh2::Channel) {
channel channel
.write_all( .write_all(
format!( format!(
"{} {} --session {} --data-dir {}\n", "{} {} --session {} --data-dir {} options --show-release-notes false --show-startup-tips false\n",
SET_ENV_VARIABLES, ZELLIJ_EXECUTABLE_LOCATION, SESSION_NAME, ZELLIJ_DATA_DIR SET_ENV_VARIABLES, ZELLIJ_EXECUTABLE_LOCATION, SESSION_NAME, ZELLIJ_DATA_DIR
) )
.as_bytes(), .as_bytes(),
@ -96,7 +96,7 @@ fn start_zellij_mirrored_session(channel: &mut ssh2::Channel) {
channel channel
.write_all( .write_all(
format!( format!(
"{} {} --session {} --data-dir {} options --mirror-session true --serialization-interval 1\n", "{} {} --session {} --data-dir {} options --show-release-notes false --show-startup-tips false --mirror-session true --serialization-interval 1\n",
SET_ENV_VARIABLES, ZELLIJ_EXECUTABLE_LOCATION, SESSION_NAME, ZELLIJ_DATA_DIR SET_ENV_VARIABLES, ZELLIJ_EXECUTABLE_LOCATION, SESSION_NAME, ZELLIJ_DATA_DIR
) )
.as_bytes(), .as_bytes(),
@ -111,7 +111,7 @@ fn start_zellij_mirrored_session_with_layout(channel: &mut ssh2::Channel, layout
channel channel
.write_all( .write_all(
format!( format!(
"{} {} --session {} --data-dir {} --new-session-with-layout {} options --mirror-session true --serialization-interval 1\n", "{} {} --session {} --data-dir {} --new-session-with-layout {} options --show-release-notes false --show-startup-tips false --mirror-session true --serialization-interval 1\n",
SET_ENV_VARIABLES, SET_ENV_VARIABLES,
ZELLIJ_EXECUTABLE_LOCATION, ZELLIJ_EXECUTABLE_LOCATION,
SESSION_NAME, SESSION_NAME,
@ -133,7 +133,7 @@ fn start_zellij_mirrored_session_with_layout_and_viewport_serialization(
channel channel
.write_all( .write_all(
format!( format!(
"{} {} --session {} --data-dir {} --new-session-with-layout {} options --mirror-session true --serialize-pane-viewport true --serialization-interval 1\n", "{} {} --session {} --data-dir {} --new-session-with-layout {} options --show-release-notes false --show-startup-tips false --mirror-session true --serialize-pane-viewport true --serialization-interval 1\n",
SET_ENV_VARIABLES, SET_ENV_VARIABLES,
ZELLIJ_EXECUTABLE_LOCATION, ZELLIJ_EXECUTABLE_LOCATION,
SESSION_NAME, SESSION_NAME,
@ -152,7 +152,7 @@ fn start_zellij_in_session(channel: &mut ssh2::Channel, session_name: &str, mirr
channel channel
.write_all( .write_all(
format!( format!(
"{} {} --session {} --data-dir {} options --mirror-session {}\n", "{} {} --session {} --data-dir {} options --show-release-notes false --show-startup-tips false --mirror-session {}\n",
SET_ENV_VARIABLES, SET_ENV_VARIABLES,
ZELLIJ_EXECUTABLE_LOCATION, ZELLIJ_EXECUTABLE_LOCATION,
session_name, session_name,
@ -185,7 +185,7 @@ fn start_zellij_without_frames(channel: &mut ssh2::Channel) {
channel channel
.write_all( .write_all(
format!( format!(
"{} {} --session {} --data-dir {} options --no-pane-frames\n", "{} {} --session {} --data-dir {} options --show-release-notes false --show-startup-tips false --no-pane-frames\n",
SET_ENV_VARIABLES, ZELLIJ_EXECUTABLE_LOCATION, SESSION_NAME, ZELLIJ_DATA_DIR SET_ENV_VARIABLES, ZELLIJ_EXECUTABLE_LOCATION, SESSION_NAME, ZELLIJ_DATA_DIR
) )
.as_bytes(), .as_bytes(),
@ -200,7 +200,7 @@ fn start_zellij_with_config(channel: &mut ssh2::Channel, config_path: &str) {
channel channel
.write_all( .write_all(
format!( format!(
"{} {} --config {} --session {} --data-dir {}\n", "{} {} --config {} --session {} --data-dir {} options --show-release-notes false --show-startup-tips false\n",
SET_ENV_VARIABLES, SET_ENV_VARIABLES,
ZELLIJ_EXECUTABLE_LOCATION, ZELLIJ_EXECUTABLE_LOCATION,
config_path, config_path,

View file

@ -336,7 +336,11 @@ impl SessionMetaData {
self.current_input_modes.insert(client_id, input_mode); self.current_input_modes.insert(client_id, input_mode);
} }
} }
pub fn propagate_configuration_changes(&mut self, config_changes: Vec<(ClientId, Config)>) { pub fn propagate_configuration_changes(
&mut self,
config_changes: Vec<(ClientId, Config)>,
config_was_written_to_disk: bool,
) {
for (client_id, new_config) in config_changes { for (client_id, new_config) in config_changes {
self.default_shell = new_config.options.default_shell.as_ref().map(|shell| { self.default_shell = new_config.options.default_shell.as_ref().map(|shell| {
TerminalAction::RunCommand(RunCommand { TerminalAction::RunCommand(RunCommand {
@ -375,6 +379,7 @@ impl SessionMetaData {
keybinds: Some(new_config.keybinds), keybinds: Some(new_config.keybinds),
default_mode: new_config.options.default_mode, default_mode: new_config.options.default_mode,
default_shell: self.default_shell.clone(), default_shell: self.default_shell.clone(),
was_written_to_disk: config_was_written_to_disk,
}) })
.unwrap(); .unwrap();
self.senders self.senders
@ -706,9 +711,12 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
// intrusive // intrusive
let setup_wizard = setup_wizard_floating_pane(); let setup_wizard = setup_wizard_floating_pane();
floating_panes.push(setup_wizard); floating_panes.push(setup_wizard);
} else if should_show_release_notes() { } else if should_show_release_notes(runtime_config_options.show_release_notes) {
let about = about_floating_pane(); let about = about_floating_pane();
floating_panes.push(about); floating_panes.push(about);
} else if should_show_startup_tip(runtime_config_options.show_startup_tips) {
let tip = tip_floating_pane();
floating_panes.push(tip);
} }
spawn_tabs( spawn_tabs(
None, None,
@ -1166,12 +1174,16 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
} }
if runtime_config_changed { if runtime_config_changed {
let config_was_written_to_disk = false;
session_data session_data
.write() .write()
.unwrap() .unwrap()
.as_mut() .as_mut()
.unwrap() .unwrap()
.propagate_configuration_changes(vec![(client_id, new_config)]); .propagate_configuration_changes(
vec![(client_id, new_config)],
config_was_written_to_disk,
);
} }
} }
}, },
@ -1183,12 +1195,13 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.unwrap() .unwrap()
.session_configuration .session_configuration
.new_saved_config(client_id, new_config); .new_saved_config(client_id, new_config);
let config_was_written_to_disk = true;
session_data session_data
.write() .write()
.unwrap() .unwrap()
.as_mut() .as_mut()
.unwrap() .unwrap()
.propagate_configuration_changes(changes); .propagate_configuration_changes(changes, config_was_written_to_disk);
}, },
ServerInstruction::FailedToWriteConfigToDisk(_client_id, file_path) => { ServerInstruction::FailedToWriteConfigToDisk(_client_id, file_path) => {
session_data session_data
@ -1227,12 +1240,16 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
} }
if runtime_config_changed { if runtime_config_changed {
let config_was_written_to_disk = false;
session_data session_data
.write() .write()
.unwrap() .unwrap()
.as_mut() .as_mut()
.unwrap() .unwrap()
.propagate_configuration_changes(vec![(client_id, new_config)]); .propagate_configuration_changes(
vec![(client_id, new_config)],
config_was_written_to_disk,
);
} }
} }
}, },
@ -1506,7 +1523,26 @@ fn about_floating_pane() -> FloatingPaneLayout {
about_pane about_pane
} }
fn should_show_release_notes() -> bool { fn tip_floating_pane() -> FloatingPaneLayout {
let mut about_pane = FloatingPaneLayout::new();
let configuration = BTreeMap::from_iter([("is_startup_tip".to_owned(), "true".to_owned())]);
about_pane.run = Some(Run::Plugin(RunPluginOrAlias::Alias(PluginAlias::new(
"about",
&Some(configuration),
None,
))));
about_pane
}
fn should_show_release_notes(should_show_release_notes_config: Option<bool>) -> bool {
if let Some(should_show_release_notes_config) = should_show_release_notes_config {
if !should_show_release_notes_config {
// if we were explicitly told not to show release notes, we don't show them,
// otherwise we make sure we only show them if they were not seen AND we know
// we are able to write to the cache
return false;
}
}
if ZELLIJ_SEEN_RELEASE_NOTES_CACHE_FILE.exists() { if ZELLIJ_SEEN_RELEASE_NOTES_CACHE_FILE.exists() {
return false; return false;
} else { } else {
@ -1521,6 +1557,10 @@ fn should_show_release_notes() -> bool {
} }
} }
fn should_show_startup_tip(should_show_startup_tip_config: Option<bool>) -> bool {
should_show_startup_tip_config.unwrap_or(true)
}
#[cfg(not(feature = "singlepass"))] #[cfg(not(feature = "singlepass"))]
fn get_engine() -> Engine { fn get_engine() -> Engine {
log::info!("Compiling plugins using Cranelift"); log::info!("Compiling plugins using Cranelift");

View file

@ -152,6 +152,7 @@ pub enum PluginInstruction {
keybinds: Option<Keybinds>, keybinds: Option<Keybinds>,
default_mode: Option<InputMode>, default_mode: Option<InputMode>,
default_shell: Option<TerminalAction>, default_shell: Option<TerminalAction>,
was_written_to_disk: bool,
}, },
FailedToWriteConfigToDisk { FailedToWriteConfigToDisk {
file_path: Option<PathBuf>, file_path: Option<PathBuf>,
@ -868,12 +869,19 @@ pub(crate) fn plugin_thread_main(
keybinds, keybinds,
default_mode, default_mode,
default_shell, default_shell,
was_written_to_disk,
} => { } => {
// TODO: notify plugins that this happened so that they can eg. rebind temporary keys that
// were lost
wasm_bridge wasm_bridge
.reconfigure(client_id, keybinds, default_mode, default_shell) .reconfigure(client_id, keybinds, default_mode, default_shell)
.non_fatal(); .non_fatal();
// TODO: notify plugins that this happened so that they can eg. rebind temporary keys that
// were lost
if was_written_to_disk {
let updates = vec![(None, None, Event::ConfigWasWrittenToDisk)];
wasm_bridge
.update_plugins(updates, shutdown_send.clone())
.non_fatal();
}
}, },
PluginInstruction::FailedToWriteConfigToDisk { file_path } => { PluginInstruction::FailedToWriteConfigToDisk { file_path } => {
let updates = vec![( let updates = vec![(

View file

@ -135,6 +135,13 @@ keybinds {
}; };
SwitchToMode "Normal" SwitchToMode "Normal"
} }
bind "a" {
LaunchOrFocusPlugin "zellij:about" {
floating true
move_to_focused_tab true
};
SwitchToMode "Normal"
}
} }
tmux { tmux {
bind "[" { SwitchToMode "Scroll"; } bind "[" { SwitchToMode "Scroll"; }
@ -414,3 +421,13 @@ load_plugins {
// Default: true // Default: true
// //
// stacked_resize false // stacked_resize false
// Whether to show release notes on first version run
// Default: true
//
// show_release_notes false
// Whether to show startup tips on session start
// Default: true
//
// show_startup_tips false

View file

@ -507,6 +507,7 @@ pub enum EventType {
HostFolderChanged = 27, HostFolderChanged = 27,
FailedToChangeHostFolder = 28, FailedToChangeHostFolder = 28,
PastedText = 29, PastedText = 29,
ConfigWasWrittenToDisk = 30,
} }
impl EventType { impl EventType {
/// String value of the enum field names used in the ProtoBuf definition. /// String value of the enum field names used in the ProtoBuf definition.
@ -545,6 +546,7 @@ impl EventType {
EventType::HostFolderChanged => "HostFolderChanged", EventType::HostFolderChanged => "HostFolderChanged",
EventType::FailedToChangeHostFolder => "FailedToChangeHostFolder", EventType::FailedToChangeHostFolder => "FailedToChangeHostFolder",
EventType::PastedText => "PastedText", EventType::PastedText => "PastedText",
EventType::ConfigWasWrittenToDisk => "ConfigWasWrittenToDisk",
} }
} }
/// Creates an enum from field names used in the ProtoBuf definition. /// Creates an enum from field names used in the ProtoBuf definition.
@ -580,6 +582,7 @@ impl EventType {
"HostFolderChanged" => Some(Self::HostFolderChanged), "HostFolderChanged" => Some(Self::HostFolderChanged),
"FailedToChangeHostFolder" => Some(Self::FailedToChangeHostFolder), "FailedToChangeHostFolder" => Some(Self::FailedToChangeHostFolder),
"PastedText" => Some(Self::PastedText), "PastedText" => Some(Self::PastedText),
"ConfigWasWrittenToDisk" => Some(Self::ConfigWasWrittenToDisk),
_ => None, _ => None,
} }
} }

View file

@ -932,6 +932,7 @@ pub enum Event {
HostFolderChanged(PathBuf), // PathBuf -> new host folder HostFolderChanged(PathBuf), // PathBuf -> new host folder
FailedToChangeHostFolder(Option<String>), // String -> the error we got when changing FailedToChangeHostFolder(Option<String>), // String -> the error we got when changing
PastedText(String), PastedText(String),
ConfigWasWrittenToDisk,
} }
#[derive( #[derive(

View file

@ -167,6 +167,18 @@ pub struct Options {
#[clap(long, value_parser)] #[clap(long, value_parser)]
#[serde(default)] #[serde(default)]
pub stacked_resize: Option<bool>, pub stacked_resize: Option<bool>,
/// Whether to show startup tips when starting a new session
/// default is true
#[clap(long, value_parser)]
#[serde(default)]
pub show_startup_tips: Option<bool>,
/// Whether to show release notes on first run of a new version
/// default is true
#[clap(long, value_parser)]
#[serde(default)]
pub show_release_notes: Option<bool>,
} }
#[derive(ArgEnum, Deserialize, Serialize, Debug, Clone, Copy, PartialEq)] #[derive(ArgEnum, Deserialize, Serialize, Debug, Clone, Copy, PartialEq)]
@ -246,6 +258,8 @@ impl Options {
.support_kitty_keyboard_protocol .support_kitty_keyboard_protocol
.or(self.support_kitty_keyboard_protocol); .or(self.support_kitty_keyboard_protocol);
let stacked_resize = other.stacked_resize.or(self.stacked_resize); let stacked_resize = other.stacked_resize.or(self.stacked_resize);
let show_startup_tips = other.show_startup_tips.or(self.show_startup_tips);
let show_release_notes = other.show_release_notes.or(self.show_release_notes);
Options { Options {
simplified_ui, simplified_ui,
@ -276,6 +290,8 @@ impl Options {
disable_session_metadata, disable_session_metadata,
support_kitty_keyboard_protocol, support_kitty_keyboard_protocol,
stacked_resize, stacked_resize,
show_startup_tips,
show_release_notes,
} }
} }
@ -335,6 +351,8 @@ impl Options {
.support_kitty_keyboard_protocol .support_kitty_keyboard_protocol
.or(self.support_kitty_keyboard_protocol); .or(self.support_kitty_keyboard_protocol);
let stacked_resize = other.stacked_resize.or(self.stacked_resize); let stacked_resize = other.stacked_resize.or(self.stacked_resize);
let show_startup_tips = other.show_startup_tips.or(self.show_startup_tips);
let show_release_notes = other.show_release_notes.or(self.show_release_notes);
Options { Options {
simplified_ui, simplified_ui,
@ -365,6 +383,8 @@ impl Options {
disable_session_metadata, disable_session_metadata,
support_kitty_keyboard_protocol, support_kitty_keyboard_protocol,
stacked_resize, stacked_resize,
show_startup_tips,
show_release_notes,
} }
} }
@ -431,6 +451,8 @@ impl From<CliOptions> for Options {
serialization_interval: opts.serialization_interval, serialization_interval: opts.serialization_interval,
support_kitty_keyboard_protocol: opts.support_kitty_keyboard_protocol, support_kitty_keyboard_protocol: opts.support_kitty_keyboard_protocol,
stacked_resize: opts.stacked_resize, stacked_resize: opts.stacked_resize,
show_release_notes: opts.show_release_notes,
show_startup_tips: opts.show_startup_tips,
..Default::default() ..Default::default()
} }
} }

View file

@ -2293,6 +2293,12 @@ impl Options {
.map(|(v, _)| v); .map(|(v, _)| v);
let stacked_resize = let stacked_resize =
kdl_property_first_arg_as_bool_or_error!(kdl_options, "stacked_resize").map(|(v, _)| v); kdl_property_first_arg_as_bool_or_error!(kdl_options, "stacked_resize").map(|(v, _)| v);
let show_startup_tips =
kdl_property_first_arg_as_bool_or_error!(kdl_options, "show_startup_tips")
.map(|(v, _)| v);
let show_release_notes =
kdl_property_first_arg_as_bool_or_error!(kdl_options, "show_release_notes")
.map(|(v, _)| v);
Ok(Options { Ok(Options {
simplified_ui, simplified_ui,
theme, theme,
@ -2322,6 +2328,8 @@ impl Options {
disable_session_metadata, disable_session_metadata,
support_kitty_keyboard_protocol, support_kitty_keyboard_protocol,
stacked_resize, stacked_resize,
show_startup_tips,
show_release_notes,
}) })
} }
pub fn from_string(stringified_keybindings: &String) -> Result<Self, ConfigError> { pub fn from_string(stringified_keybindings: &String) -> Result<Self, ConfigError> {
@ -3149,6 +3157,56 @@ impl Options {
None None
} }
} }
fn show_startup_tips_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
let comment_text = format!(
"{}\n{}\n{}\n{}",
" ", "// Whether to show tips on startup", "// Default: true", "// ",
);
let create_node = |node_value: bool| -> KdlNode {
let mut node = KdlNode::new("show_startup_tips");
node.push(KdlValue::Bool(node_value));
node
};
if let Some(show_startup_tips) = self.show_startup_tips {
let mut node = create_node(show_startup_tips);
if add_comments {
node.set_leading(format!("{}\n", comment_text));
}
Some(node)
} else if add_comments {
let mut node = create_node(false);
node.set_leading(format!("{}\n// ", comment_text));
Some(node)
} else {
None
}
}
fn show_release_notes_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
let comment_text = format!(
"{}\n{}\n{}\n{}",
" ", "// Whether to show release notes on first version run", "// Default: true", "// ",
);
let create_node = |node_value: bool| -> KdlNode {
let mut node = KdlNode::new("show_release_notes");
node.push(KdlValue::Bool(node_value));
node
};
if let Some(show_release_notes) = self.show_release_notes {
let mut node = create_node(show_release_notes);
if add_comments {
node.set_leading(format!("{}\n", comment_text));
}
Some(node)
} else if add_comments {
let mut node = create_node(false);
node.set_leading(format!("{}\n// ", comment_text));
Some(node)
} else {
None
}
}
pub fn to_kdl(&self, add_comments: bool) -> Vec<KdlNode> { pub fn to_kdl(&self, add_comments: bool) -> Vec<KdlNode> {
let mut nodes = vec![]; let mut nodes = vec![];
if let Some(simplified_ui_node) = self.simplified_ui_to_kdl(add_comments) { if let Some(simplified_ui_node) = self.simplified_ui_to_kdl(add_comments) {
@ -3239,6 +3297,12 @@ impl Options {
if let Some(stacked_resize) = self.stacked_resize_to_kdl(add_comments) { if let Some(stacked_resize) = self.stacked_resize_to_kdl(add_comments) {
nodes.push(stacked_resize); nodes.push(stacked_resize);
} }
if let Some(show_startup_tips) = self.show_startup_tips_to_kdl(add_comments) {
nodes.push(show_startup_tips);
}
if let Some(show_release_notes) = self.show_release_notes_to_kdl(add_comments) {
nodes.push(show_release_notes);
}
nodes nodes
} }
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/kdl/mod.rs source: zellij-utils/src/kdl/mod.rs
assertion_line: 5626 assertion_line: 5922
expression: fake_config_stringified expression: fake_config_stringified
--- ---
keybinds clear-defaults=true { keybinds clear-defaults=true {
@ -100,6 +100,13 @@ keybinds clear-defaults=true {
bind "w" { SearchToggleOption "Wrap"; } bind "w" { SearchToggleOption "Wrap"; }
} }
session { session {
bind "a" {
LaunchOrFocusPlugin "zellij:about" {
floating true
move_to_focused_tab true
}
SwitchToMode "normal"
}
bind "c" { bind "c" {
LaunchOrFocusPlugin "configuration" { LaunchOrFocusPlugin "configuration" {
floating true floating true

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/kdl/mod.rs source: zellij-utils/src/kdl/mod.rs
assertion_line: 5638 assertion_line: 5934
expression: fake_config_stringified expression: fake_config_stringified
--- ---
keybinds clear-defaults=true { keybinds clear-defaults=true {
@ -100,6 +100,13 @@ keybinds clear-defaults=true {
bind "w" { SearchToggleOption "Wrap"; } bind "w" { SearchToggleOption "Wrap"; }
} }
session { session {
bind "a" {
LaunchOrFocusPlugin "zellij:about" {
floating true
move_to_focused_tab true
}
SwitchToMode "normal"
}
bind "c" { bind "c" {
LaunchOrFocusPlugin "configuration" { LaunchOrFocusPlugin "configuration" {
floating true floating true
@ -439,3 +446,13 @@ load_plugins {
// //
// stacked_resize false // stacked_resize false
// Whether to show tips on startup
// Default: true
//
// show_startup_tips false
// Whether to show release notes on first version run
// Default: true
//
// show_release_notes false

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/kdl/mod.rs source: zellij-utils/src/kdl/mod.rs
assertion_line: 5537 assertion_line: 5873
expression: fake_document.to_string() expression: fake_document.to_string()
--- ---
@ -186,3 +186,13 @@ support_kitty_keyboard_protocol false
// //
// stacked_resize false // stacked_resize false
// Whether to show tips on startup
// Default: true
//
// show_startup_tips false
// Whether to show release notes on first version run
// Default: true
//
// show_release_notes false

View file

@ -53,6 +53,7 @@ enum EventType {
HostFolderChanged = 27; HostFolderChanged = 27;
FailedToChangeHostFolder = 28; FailedToChangeHostFolder = 28;
PastedText = 29; PastedText = 29;
ConfigWasWrittenToDisk = 30;
} }
message EventNameList { message EventNameList {

View file

@ -355,6 +355,10 @@ impl TryFrom<ProtobufEvent> for Event {
}, },
_ => Err("Malformed payload for the PastedText Event"), _ => Err("Malformed payload for the PastedText Event"),
}, },
Some(ProtobufEventType::ConfigWasWrittenToDisk) => match protobuf_event.payload {
None => Ok(Event::ConfigWasWrittenToDisk),
_ => Err("Malformed payload for the ConfigWasWrittenToDisk Event"),
},
None => Err("Unknown Protobuf Event"), None => Err("Unknown Protobuf Event"),
} }
} }
@ -725,6 +729,10 @@ impl TryFrom<Event> for ProtobufEvent {
pasted_text, pasted_text,
})), })),
}), }),
Event::ConfigWasWrittenToDisk => Ok(ProtobufEvent {
name: ProtobufEventType::ConfigWasWrittenToDisk as i32,
payload: None,
}),
} }
} }
} }
@ -1306,6 +1314,7 @@ impl TryFrom<ProtobufEventType> for EventType {
ProtobufEventType::HostFolderChanged => EventType::HostFolderChanged, ProtobufEventType::HostFolderChanged => EventType::HostFolderChanged,
ProtobufEventType::FailedToChangeHostFolder => EventType::FailedToChangeHostFolder, ProtobufEventType::FailedToChangeHostFolder => EventType::FailedToChangeHostFolder,
ProtobufEventType::PastedText => EventType::PastedText, ProtobufEventType::PastedText => EventType::PastedText,
ProtobufEventType::ConfigWasWrittenToDisk => EventType::ConfigWasWrittenToDisk,
}) })
} }
} }
@ -1344,6 +1353,7 @@ impl TryFrom<EventType> for ProtobufEventType {
EventType::HostFolderChanged => ProtobufEventType::HostFolderChanged, EventType::HostFolderChanged => ProtobufEventType::HostFolderChanged,
EventType::FailedToChangeHostFolder => ProtobufEventType::FailedToChangeHostFolder, EventType::FailedToChangeHostFolder => ProtobufEventType::FailedToChangeHostFolder,
EventType::PastedText => ProtobufEventType::PastedText, EventType::PastedText => ProtobufEventType::PastedText,
EventType::ConfigWasWrittenToDisk => ProtobufEventType::ConfigWasWrittenToDisk,
}) })
} }
} }

View file

@ -34,4 +34,6 @@ Options {
disable_session_metadata: None, disable_session_metadata: None,
support_kitty_keyboard_protocol: None, support_kitty_keyboard_protocol: None,
stacked_resize: None, stacked_resize: None,
show_startup_tips: None,
show_release_notes: None,
} }

View file

@ -34,4 +34,6 @@ Options {
disable_session_metadata: None, disable_session_metadata: None,
support_kitty_keyboard_protocol: None, support_kitty_keyboard_protocol: None,
stacked_resize: None, stacked_resize: None,
show_startup_tips: None,
show_release_notes: None,
} }

View file

@ -32,4 +32,6 @@ Options {
disable_session_metadata: None, disable_session_metadata: None,
support_kitty_keyboard_protocol: None, support_kitty_keyboard_protocol: None,
stacked_resize: None, stacked_resize: None,
show_startup_tips: None,
show_release_notes: None,
} }

View file

@ -3956,6 +3956,36 @@ Config {
}: [ }: [
NextSwapLayout, NextSwapLayout,
], ],
KeyWithModifier {
bare_key: Char(
'a',
),
key_modifiers: {},
}: [
LaunchOrFocusPlugin(
RunPlugin(
RunPlugin {
_allow_exec_host_cmd: false,
location: Zellij(
PluginTag(
"about",
),
),
configuration: PluginUserConfiguration(
{},
),
initial_cwd: None,
},
),
true,
true,
false,
false,
),
SwitchToMode(
Normal,
),
],
KeyWithModifier { KeyWithModifier {
bare_key: Char( bare_key: Char(
'b', 'b',
@ -5611,6 +5641,8 @@ Config {
disable_session_metadata: None, disable_session_metadata: None,
support_kitty_keyboard_protocol: None, support_kitty_keyboard_protocol: None,
stacked_resize: None, stacked_resize: None,
show_startup_tips: None,
show_release_notes: None,
}, },
themes: {}, themes: {},
plugins: PluginAliases { plugins: PluginAliases {

View file

@ -3956,6 +3956,36 @@ Config {
}: [ }: [
NextSwapLayout, NextSwapLayout,
], ],
KeyWithModifier {
bare_key: Char(
'a',
),
key_modifiers: {},
}: [
LaunchOrFocusPlugin(
RunPlugin(
RunPlugin {
_allow_exec_host_cmd: false,
location: Zellij(
PluginTag(
"about",
),
),
configuration: PluginUserConfiguration(
{},
),
initial_cwd: None,
},
),
true,
true,
false,
false,
),
SwitchToMode(
Normal,
),
],
KeyWithModifier { KeyWithModifier {
bare_key: Char( bare_key: Char(
'b', 'b',
@ -5611,6 +5641,8 @@ Config {
disable_session_metadata: None, disable_session_metadata: None,
support_kitty_keyboard_protocol: None, support_kitty_keyboard_protocol: None,
stacked_resize: None, stacked_resize: None,
show_startup_tips: None,
show_release_notes: None,
}, },
themes: {}, themes: {},
plugins: PluginAliases { plugins: PluginAliases {

View file

@ -119,6 +119,8 @@ Config {
disable_session_metadata: None, disable_session_metadata: None,
support_kitty_keyboard_protocol: None, support_kitty_keyboard_protocol: None,
stacked_resize: None, stacked_resize: None,
show_startup_tips: None,
show_release_notes: None,
}, },
themes: {}, themes: {},
plugins: PluginAliases { plugins: PluginAliases {

View file

@ -34,4 +34,6 @@ Options {
disable_session_metadata: None, disable_session_metadata: None,
support_kitty_keyboard_protocol: None, support_kitty_keyboard_protocol: None,
stacked_resize: None, stacked_resize: None,
show_startup_tips: None,
show_release_notes: None,
} }

View file

@ -1,5 +1,6 @@
--- ---
source: zellij-utils/src/setup.rs source: zellij-utils/src/setup.rs
assertion_line: 841
expression: "format!(\"{:#?}\", config)" expression: "format!(\"{:#?}\", config)"
--- ---
Config { Config {
@ -3955,6 +3956,36 @@ Config {
}: [ }: [
NextSwapLayout, NextSwapLayout,
], ],
KeyWithModifier {
bare_key: Char(
'a',
),
key_modifiers: {},
}: [
LaunchOrFocusPlugin(
RunPlugin(
RunPlugin {
_allow_exec_host_cmd: false,
location: Zellij(
PluginTag(
"about",
),
),
configuration: PluginUserConfiguration(
{},
),
initial_cwd: None,
},
),
true,
true,
false,
false,
),
SwitchToMode(
Normal,
),
],
KeyWithModifier { KeyWithModifier {
bare_key: Char( bare_key: Char(
'b', 'b',
@ -5610,6 +5641,8 @@ Config {
disable_session_metadata: None, disable_session_metadata: None,
support_kitty_keyboard_protocol: None, support_kitty_keyboard_protocol: None,
stacked_resize: None, stacked_resize: None,
show_startup_tips: None,
show_release_notes: None,
}, },
themes: { themes: {
"other-theme-from-config": Theme { "other-theme-from-config": Theme {

View file

@ -3956,6 +3956,36 @@ Config {
}: [ }: [
NextSwapLayout, NextSwapLayout,
], ],
KeyWithModifier {
bare_key: Char(
'a',
),
key_modifiers: {},
}: [
LaunchOrFocusPlugin(
RunPlugin(
RunPlugin {
_allow_exec_host_cmd: false,
location: Zellij(
PluginTag(
"about",
),
),
configuration: PluginUserConfiguration(
{},
),
initial_cwd: None,
},
),
true,
true,
false,
false,
),
SwitchToMode(
Normal,
),
],
KeyWithModifier { KeyWithModifier {
bare_key: Char( bare_key: Char(
'b', 'b',
@ -5611,6 +5641,8 @@ Config {
disable_session_metadata: None, disable_session_metadata: None,
support_kitty_keyboard_protocol: None, support_kitty_keyboard_protocol: None,
stacked_resize: None, stacked_resize: None,
show_startup_tips: None,
show_release_notes: None,
}, },
themes: {}, themes: {},
plugins: PluginAliases { plugins: PluginAliases {