feat(ux): first run setup-wizard (#3556)

* separate saved/runtime structure, kind of working

* serializing config

* work

* work

* save config through the configuration screen

* work

* startup wizard

* style(code): cleanups

* fix(session): reload config from disk when switching sessions

* style(fmt): rustfmt

* fix(config): propagate cli config options to screen

* style(fmt): rustfmt
This commit is contained in:
Aram Drevekenin 2024-08-16 14:00:01 +02:00 committed by GitHub
parent c25166c30a
commit 056537d3ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
53 changed files with 4985 additions and 314 deletions

View file

@ -2,6 +2,8 @@ use zellij_tile::prelude::*;
use std::collections::{BTreeMap, BTreeSet};
static UI_SIZE: usize = 15;
struct State {
userspace_configuration: BTreeMap<String, String>,
selected_index: Option<usize>,
@ -16,6 +18,9 @@ struct State {
preset_color_index: usize,
primary_leader_key_color_index: usize,
secondary_leader_key_color_index: usize,
notification: Option<String>,
is_setup_wizard: bool,
ui_size: usize,
}
impl Default for State {
@ -43,6 +48,9 @@ impl Default for State {
secondary_leader_key_color_index: 0,
mode_color_index: 2,
preset_color_index: 1,
notification: None,
is_setup_wizard: false,
ui_size: UI_SIZE,
}
}
}
@ -51,6 +59,10 @@ register_plugin!(State);
impl ZellijPlugin for State {
fn load(&mut self, configuration: BTreeMap<String, String>) {
self.is_setup_wizard = configuration
.get("is_setup_wizard")
.map(|v| v == "true")
.unwrap_or(false);
self.userspace_configuration = configuration;
// we need the ReadApplicationState permission to receive the ModeUpdate and TabUpdate
// events
@ -64,12 +76,19 @@ impl ZellijPlugin for State {
PermissionType::ChangeApplicationState,
]);
subscribe(&[
EventType::ModeUpdate,
EventType::TabUpdate,
EventType::Key,
EventType::Timer,
EventType::PermissionRequestResult,
EventType::Key,
EventType::FailedToWriteConfigToDisk,
]);
if self.is_setup_wizard {
self.ui_size = 18;
self.selected_index = Some(0);
let own_plugin_id = get_plugin_ids().plugin_id;
rename_plugin_pane(own_plugin_id, "First Run Setup Wizard (Step 1/1)");
resize_focused_pane(Resize::Increase);
resize_focused_pane(Resize::Increase);
resize_focused_pane(Resize::Increase);
}
}
fn update(&mut self, event: Event) -> bool {
let mut should_render = false;
@ -80,10 +99,26 @@ impl ZellijPlugin for State {
Event::Key(key) => {
if self.remapping_leaders {
should_render = self.handle_remapping_screen_key(key);
} else if self.is_setup_wizard {
should_render = self.handle_setup_wizard_key(key);
} else {
should_render = self.handle_main_screen_key(key);
}
},
Event::FailedToWriteConfigToDisk(config_file_path) => {
match config_file_path {
Some(failed_path) => {
self.notification = Some(format!(
"Failed to write configuration file: {}",
failed_path
));
},
None => {
self.notification = Some(format!("Failed to write configuration file."));
},
}
should_render = true;
},
_ => (),
};
should_render
@ -91,6 +126,8 @@ impl ZellijPlugin for State {
fn render(&mut self, rows: usize, cols: usize) {
if self.remapping_leaders {
self.render_remapping_leaders_screen(rows, cols);
} else if self.is_setup_wizard {
self.render_setup_wizard_screen(rows, cols);
} else {
self.render_main_screen(rows, cols);
}
@ -185,7 +222,10 @@ impl State {
}
fn handle_main_screen_key(&mut self, key: KeyWithModifier) -> bool {
let mut should_render = false;
if key.bare_key == BareKey::Down && key.has_no_modifiers() {
if self.notification.is_some() {
self.notification = None;
should_render = true;
} else if key.bare_key == BareKey::Down && key.has_no_modifiers() {
if self.selected_index.is_none() {
self.selected_index = Some(0);
} else if self.selected_index < Some(1) {
@ -205,45 +245,70 @@ impl State {
should_render = true;
} else if key.bare_key == BareKey::Enter && key.has_no_modifiers() {
if let Some(selected) = self.selected_index.take() {
if selected == 0 {
// TODO: these should be part of a "transaction" when they are
// implemented
reconfigure(default_keybinds(
self.primary_modifier
.iter()
.map(|m| m.to_string())
.collect::<Vec<_>>()
.join(" "),
self.secondary_modifier
.iter()
.map(|m| m.to_string())
.collect::<Vec<_>>()
.join(" "),
));
switch_to_input_mode(&InputMode::Normal);
} else if selected == 1 {
// TODO: these should be part of a "transaction" when they are
// implemented
reconfigure(unlock_first_keybinds(
self.primary_modifier
.iter()
.map(|m| m.to_string())
.collect::<Vec<_>>()
.join(" "),
self.secondary_modifier
.iter()
.map(|m| m.to_string())
.collect::<Vec<_>>()
.join(" "),
));
switch_to_input_mode(&InputMode::Locked);
let write_to_disk = false;
self.reconfigure(selected, write_to_disk);
self.notification = Some("Configuration applied to current session.".to_owned());
should_render = true;
} else {
self.selected_index = Some(0);
should_render = true;
}
} else if key.bare_key == BareKey::Char(' ') && key.has_no_modifiers() {
if let Some(selected) = self.selected_index.take() {
let write_to_disk = true;
self.reconfigure(selected, write_to_disk);
self.notification = Some("Configuration applied and saved to disk.".to_owned());
should_render = true;
}
} else if key.bare_key == BareKey::Char('l') && key.has_no_modifiers() {
self.remapping_leaders = true;
should_render = true;
} else if key.bare_key == BareKey::Esc && key.has_no_modifiers() {
} else if (key.bare_key == BareKey::Esc && key.has_no_modifiers())
|| key.is_key_with_ctrl_modifier(BareKey::Char('c'))
{
close_self();
should_render = true;
}
should_render
}
fn handle_setup_wizard_key(&mut self, key: KeyWithModifier) -> bool {
let mut should_render = false;
if self.notification.is_some() {
self.notification = None;
should_render = true;
} else if key.bare_key == BareKey::Down && key.has_no_modifiers() {
if self.selected_index.is_none() {
self.selected_index = Some(0);
} else if self.selected_index < Some(1) {
self.selected_index = Some(1);
} else {
self.selected_index = None;
}
should_render = true;
} else if key.bare_key == BareKey::Up && key.has_no_modifiers() {
if self.selected_index.is_none() {
self.selected_index = Some(1);
} else if self.selected_index == Some(1) {
self.selected_index = Some(0);
} else {
self.selected_index = None;
}
should_render = true;
} else if key.bare_key == BareKey::Enter && key.has_no_modifiers() {
if let Some(selected) = self.selected_index.take() {
let write_to_disk = true;
self.reconfigure(selected, write_to_disk);
close_self();
} else {
self.selected_index = Some(0);
should_render = true;
}
} else if key.bare_key == BareKey::Char('l') && key.has_no_modifiers() {
self.remapping_leaders = true;
should_render = true;
} else if (key.bare_key == BareKey::Esc && key.has_no_modifiers())
|| key.is_key_with_ctrl_modifier(BareKey::Char('c'))
{
close_self();
should_render = true;
}
@ -412,7 +477,7 @@ impl State {
print_text_with_coordinates(
Text::new(title_text).color_range(2, ..),
left_padding,
rows.saturating_sub(15) / 2,
rows.saturating_sub(self.ui_size) / 2,
None,
None,
);
@ -426,7 +491,91 @@ impl State {
print_text_with_coordinates(
Text::new(title_text).color_range(2, ..),
left_padding,
rows.saturating_sub(15) / 2,
rows.saturating_sub(self.ui_size) / 2,
None,
None,
);
}
}
fn render_setup_wizard_title(&self, rows: usize, cols: usize, primary_modifier_key_text: &str) {
let widths = self.main_screen_widths(primary_modifier_key_text);
if cols >= widths.0 {
let title_text_1 = "Hi there! How would you like to interact with Zellij?";
let title_text_2 = "Not sure? Press <ENTER> to choose Default.";
let title_text_3 = "Everything can always be changed later.";
let title_text_4 = "Tips appear on screen - you don't need to remember anything.";
let left_padding = cols.saturating_sub(widths.0) / 2;
let first_row_coords = (rows.saturating_sub(self.ui_size) / 2).saturating_sub(1);
print_text_with_coordinates(
Text::new(title_text_1).color_range(2, ..),
left_padding,
first_row_coords,
None,
None,
);
print_text_with_coordinates(
Text::new(title_text_2)
.color_range(0, ..10)
.color_range(2, 16..23)
.color_range(self.preset_color_index, 34..41),
left_padding,
first_row_coords + 2,
None,
None,
);
print_text_with_coordinates(
Text::new(title_text_3),
left_padding,
first_row_coords + 4,
None,
None,
);
print_text_with_coordinates(
Text::new(title_text_4),
left_padding,
first_row_coords + 5,
None,
None,
);
} else {
let title_text_1 = "Hi there! Which do you prefer?";
let title_text_2 = "Not sure? Press <ENTER>";
let title_text_3 = "Can be changed later. Tips appear";
let title_text_4 = "on screen - no need to remember";
let left_padding = if cols >= widths.1 {
cols.saturating_sub(widths.1) / 2
} else {
cols.saturating_sub(widths.2) / 2
};
let first_row_coords = (rows.saturating_sub(self.ui_size) / 2).saturating_sub(1);
print_text_with_coordinates(
Text::new(title_text_1).color_range(2, ..),
left_padding,
first_row_coords,
None,
None,
);
print_text_with_coordinates(
Text::new(title_text_2)
.color_range(0, ..10)
.color_range(2, 16..23)
.color_range(self.preset_color_index, 40..49),
left_padding,
first_row_coords + 2,
None,
None,
);
print_text_with_coordinates(
Text::new(title_text_3),
left_padding,
first_row_coords + 4,
None,
None,
);
print_text_with_coordinates(
Text::new(title_text_4),
left_padding,
first_row_coords + 5,
None,
None,
);
@ -455,7 +604,7 @@ impl State {
)
.indent(1),
NestedListItem::new(format!(
"{} t - to enter TAB mode.",
"{} t - to enter TAB mode",
primary_modifier_key_text
))
.color_range(
@ -488,7 +637,7 @@ impl State {
primary_modifier_key_text_len + 14..primary_modifier_key_text_len + 18,
),
NestedListItem::new(format!(
"{} t - to enter TAB mode.",
"{} t - to enter TAB mode",
primary_modifier_key_text
))
.indent(1)
@ -517,7 +666,7 @@ impl State {
primary_modifier_key_text_len + 5..primary_modifier_key_text_len + 10,
)
.indent(1),
NestedListItem::new(format!("{} t - TAB mode.", primary_modifier_key_text))
NestedListItem::new(format!("{} t - TAB mode", primary_modifier_key_text))
.color_range(
self.primary_leader_key_color_index,
..primary_modifier_key_text_len + 3,
@ -536,9 +685,9 @@ impl State {
}
let left_padding = cols.saturating_sub(max_width) / 2;
let top_coordinates = if rows > 14 {
(rows.saturating_sub(15) / 2) + 2
(rows.saturating_sub(self.ui_size) / 2) + 2
} else {
(rows.saturating_sub(15) / 2) + 1
(rows.saturating_sub(self.ui_size) / 2) + 1
};
print_nested_list_with_coordinates(
list_items,
@ -578,7 +727,7 @@ impl State {
primary_modifier_key_text_len + 16..primary_modifier_key_text_len + 21,
),
NestedListItem::new(format!(
"{} g + t to enter TAB mode.",
"{} g + t to enter TAB mode",
primary_modifier_key_text
))
.indent(1)
@ -623,7 +772,7 @@ impl State {
)
.indent(1),
NestedListItem::new(format!(
"{} g + t to enter TAB mode.",
"{} g + t to enter TAB mode",
primary_modifier_key_text
))
.color_range(
@ -664,7 +813,7 @@ impl State {
primary_modifier_key_text_len + 7..primary_modifier_key_text_len + 11,
)
.indent(1),
NestedListItem::new(format!("{} g + t TAB mode.", primary_modifier_key_text))
NestedListItem::new(format!("{} g + t TAB mode", primary_modifier_key_text))
.color_range(
self.primary_leader_key_color_index,
..primary_modifier_key_text_len + 3,
@ -687,9 +836,9 @@ impl State {
}
let left_padding = cols.saturating_sub(max_width) / 2;
let top_coordinates = if rows > 14 {
(rows.saturating_sub(15) / 2) + 7
(rows.saturating_sub(self.ui_size) / 2) + 7
} else {
(rows.saturating_sub(15) / 2) + 5
(rows.saturating_sub(self.ui_size) / 2) + 5
};
print_nested_list_with_coordinates(
list_items,
@ -710,14 +859,14 @@ impl State {
let primary_modifier_key_text_len = primary_modifier_key_text.chars().count();
let secondary_modifier_key_text_len = secondary_modifier_key_text.chars().count();
let top_coordinates = if rows > 14 {
(rows.saturating_sub(15) / 2) + 12
(rows.saturating_sub(self.ui_size) / 2) + 12
} else {
(rows.saturating_sub(15) / 2) + 9
(rows.saturating_sub(self.ui_size) / 2) + 9
};
if cols >= widths.0 {
let leader_key_text = format!(
"Leader keys: {} - modes, {} - quicknav and shortcuts.",
"Leader keys: {} - modes, {} - quicknav and shortcuts",
primary_modifier_key_text, secondary_modifier_key_text
);
let left_padding = cols.saturating_sub(widths.0) / 2;
@ -767,12 +916,12 @@ impl State {
)
};
}
fn render_warning_if_needed(&self, rows: usize, cols: usize, primary_modifier_key_text: &str) {
fn render_info_line(&self, rows: usize, cols: usize, primary_modifier_key_text: &str) {
let widths = self.main_screen_widths(primary_modifier_key_text);
let top_coordinates = if rows > 14 {
(rows.saturating_sub(15) / 2) + 14
(rows.saturating_sub(self.ui_size) / 2) + 14
} else {
(rows.saturating_sub(15) / 2) + 10
(rows.saturating_sub(self.ui_size) / 2) + 10
};
let left_padding = if cols >= widths.0 {
cols.saturating_sub(widths.0) / 2
@ -781,7 +930,15 @@ impl State {
} else {
cols.saturating_sub(widths.2) / 2
};
if let Some(warning_text) = self.warning_text(cols) {
if let Some(notification) = &self.notification {
print_text_with_coordinates(
Text::new(notification).color_range(3, ..),
left_padding,
top_coordinates,
None,
None,
);
} else if let Some(warning_text) = self.warning_text(cols) {
print_text_with_coordinates(
Text::new(warning_text).color_range(3, ..),
left_padding,
@ -791,24 +948,56 @@ impl State {
);
}
}
fn render_help_text_main(&self, rows: usize, cols: usize, primary_modifier_key_text: &str) {
let widths = self.main_screen_widths(primary_modifier_key_text);
if cols >= widths.0 {
let help_text = "Help: <↓↑/ENTER> - navigate/select, <l> - leaders, <ESC> - close";
fn render_help_text_main(&self, rows: usize, cols: usize) {
let full_help_text = "Help: <↓↑> - navigate, <ENTER> - apply, <SPACE> - apply & save, <l> - leaders, <ESC> - close";
let short_help_text = "Help: <↓↑> / <ENTER> / <SPACE> / <l> / <ESC>";
if cols >= full_help_text.chars().count() {
print_text_with_coordinates(
Text::new(help_text)
.color_range(2, 6..16)
.color_range(2, 36..39)
.color_range(2, 51..56),
Text::new(full_help_text)
.color_range(2, 6..10)
.color_range(2, 23..30)
.color_range(2, 40..47)
.color_range(2, 64..67)
.color_range(2, 79..84),
0,
rows,
None,
None,
);
} else {
let help_text = "Help: <↓↑> / <ENTER> / <l> / <ESC>";
print_text_with_coordinates(
Text::new(help_text)
Text::new(short_help_text)
.color_range(2, 6..10)
.color_range(2, 13..20)
.color_range(2, 23..30)
.color_range(2, 33..36)
.color_range(2, 39..44),
0,
rows,
None,
None,
);
}
}
fn render_help_text_setup_wizard(&self, rows: usize, cols: usize) {
let full_help_text =
"Help: <↓↑> - navigate, <ENTER> - apply & save, <l> - change leaders, <ESC> - close";
let short_help_text = "Help: <↓↑> / <ENTER> / <l> / <ESC>";
if cols >= full_help_text.chars().count() {
print_text_with_coordinates(
Text::new(full_help_text)
.color_range(2, 6..10)
.color_range(2, 23..30)
.color_range(2, 47..50)
.color_range(2, 69..74),
0,
rows,
None,
None,
);
} else {
print_text_with_coordinates(
Text::new(short_help_text)
.color_range(2, 6..10)
.color_range(2, 13..20)
.color_range(2, 23..26)
@ -892,8 +1081,23 @@ impl State {
&primary_modifier_key_text,
&secondary_modifier_key_text,
);
self.render_warning_if_needed(rows, cols, &primary_modifier_key_text);
self.render_help_text_main(rows, cols, &primary_modifier_key_text);
self.render_info_line(rows, cols, &primary_modifier_key_text);
self.render_help_text_main(rows, cols);
}
fn render_setup_wizard_screen(&mut self, rows: usize, cols: usize) {
let primary_modifier_key_text = self.primary_modifier_text();
let secondary_modifier_key_text = self.secondary_modifier_text();
self.render_setup_wizard_title(rows, cols, &primary_modifier_key_text);
self.render_first_bulletin(rows + 8, cols, &primary_modifier_key_text);
self.render_second_bulletin(rows + 8, cols, &primary_modifier_key_text);
self.render_leader_keys_indication(
rows + 8,
cols,
&primary_modifier_key_text,
&secondary_modifier_key_text,
);
self.render_info_line(rows + 8, cols, &primary_modifier_key_text);
self.render_help_text_setup_wizard(rows + 8, cols);
}
fn warning_text(&self, max_width: usize) -> Option<String> {
if self.needs_kitty_support() {
@ -921,6 +1125,47 @@ impl State {
|| self.primary_modifier.contains(&KeyModifier::Super)
|| self.secondary_modifier.contains(&KeyModifier::Super)
}
fn reconfigure(&self, selected: usize, write_to_disk: bool) {
if selected == 0 {
// TODO: these should be part of a "transaction" when they are
// implemented
reconfigure(
default_keybinds(
self.primary_modifier
.iter()
.map(|m| m.to_string())
.collect::<Vec<_>>()
.join(" "),
self.secondary_modifier
.iter()
.map(|m| m.to_string())
.collect::<Vec<_>>()
.join(" "),
),
write_to_disk,
);
switch_to_input_mode(&InputMode::Normal);
} else if selected == 1 {
// TODO: these should be part of a "transaction" when they are
// implemented
reconfigure(
unlock_first_keybinds(
self.primary_modifier
.iter()
.map(|m| m.to_string())
.collect::<Vec<_>>()
.join(" "),
self.secondary_modifier
.iter()
.map(|m| m.to_string())
.collect::<Vec<_>>()
.join(" "),
),
write_to_disk,
);
switch_to_input_mode(&InputMode::Locked);
}
}
}
fn unlock_first_keybinds(primary_modifier: String, secondary_modifier: String) -> String {

View file

@ -332,6 +332,7 @@ impl ZellijPlugin for State {
);
},
BareKey::Char('0') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
let write_to_disk = true;
reconfigure(
"
keybinds {
@ -341,6 +342,7 @@ impl ZellijPlugin for State {
}
"
.to_owned(),
write_to_disk,
);
},
BareKey::Char('a') if key.has_modifiers(&[KeyModifier::Alt]) => {

View file

@ -386,8 +386,13 @@ fn attach_with_session_name(
pub(crate) fn start_client(opts: CliArgs) {
// look for old YAML config/layout/theme files and convert them to KDL
convert_old_yaml_files(&opts);
let (config, layout, config_options, config_without_layout, config_options_without_layout) =
match Setup::from_cli_args(&opts) {
let (
config,
layout,
config_options,
mut config_without_layout,
mut config_options_without_layout,
) = match Setup::from_cli_args(&opts) {
Ok(results) => results,
Err(e) => {
if let ConfigError::KdlError(error) = e {
@ -399,6 +404,7 @@ pub(crate) fn start_client(opts: CliArgs) {
process::exit(1);
},
};
let mut reconnect_to_session: Option<ConnectToSession> = None;
let os_input = get_os_input(get_client_os_input);
loop {
@ -415,6 +421,11 @@ pub(crate) fn start_client(opts: CliArgs) {
// untested and pretty involved function
//
// ideally, we should write tests for this whole function and refctor it
reload_config_from_disk(
&mut config_without_layout,
&mut config_options_without_layout,
&opts,
);
if reconnect_to_session.name.is_some() {
opts.command = Some(Command::Sessions(Sessions::Attach {
session_name: reconnect_to_session.name.clone(),
@ -711,3 +722,19 @@ pub(crate) fn list_aliases(opts: CliArgs) {
}
process::exit(0);
}
fn reload_config_from_disk(
config_without_layout: &mut Config,
config_options_without_layout: &mut Options,
opts: &CliArgs,
) {
match Setup::from_cli_args(&opts) {
Ok((_, _, _, reloaded_config_without_layout, reloaded_config_options_without_layout)) => {
*config_without_layout = reloaded_config_without_layout;
*config_options_without_layout = reloaded_config_options_without_layout;
},
Err(e) => {
log::error!("Failed to reload config: {}", e);
},
};
}

View file

@ -12,6 +12,7 @@ use log::info;
use std::env::current_exe;
use std::io::{self, Write};
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use std::sync::{Arc, Mutex};
use std::thread;
@ -53,6 +54,7 @@ pub(crate) enum ClientInstruction {
UnblockCliPipeInput(String), // String -> pipe name
CliPipeOutput(String, String), // String -> pipe name, String -> output
QueryTerminalSize,
WriteConfigToDisk { config: String },
}
impl From<ServerToClientMsg> for ClientInstruction {
@ -75,6 +77,9 @@ impl From<ServerToClientMsg> for ClientInstruction {
ClientInstruction::CliPipeOutput(pipe_name, output)
},
ServerToClientMsg::QueryTerminalSize => ClientInstruction::QueryTerminalSize,
ServerToClientMsg::WriteConfigToDisk { config } => {
ClientInstruction::WriteConfigToDisk { config }
},
}
}
}
@ -97,6 +102,7 @@ impl From<&ClientInstruction> for ClientContext {
ClientInstruction::UnblockCliPipeInput(..) => ClientContext::UnblockCliPipeInput,
ClientInstruction::CliPipeOutput(..) => ClientContext::CliPipeOutput,
ClientInstruction::QueryTerminalSize => ClientContext::QueryTerminalSize,
ClientInstruction::WriteConfigToDisk { .. } => ClientContext::WriteConfigToDisk,
}
}
}
@ -158,8 +164,8 @@ pub(crate) enum InputInstruction {
pub fn start_client(
mut os_input: Box<dyn ClientOsApi>,
opts: CliArgs,
config: Config,
config_options: Options,
config: Config, // saved to disk (or default?)
config_options: Options, // CLI options merged into (getting priority over) saved config options
info: ClientInfo,
layout: Option<Layout>,
tab_position_to_focus: Option<usize>,
@ -218,7 +224,6 @@ pub fn start_client(
rounded_corners: config.ui.pane_frames.rounded_corners,
hide_session_name: config.ui.pane_frames.hide_session_name,
},
keybinds: config.keybinds.clone(),
};
let create_ipc_pipe = || -> std::path::PathBuf {
@ -238,7 +243,8 @@ pub fn start_client(
(
ClientToServerMsg::AttachClient(
client_attributes,
config_options,
config.clone(),
config_options.clone(),
tab_position_to_focus,
pane_id_to_focus,
),
@ -251,14 +257,25 @@ pub fn start_client(
let ipc_pipe = create_ipc_pipe();
spawn_server(&*ipc_pipe, opts.debug).unwrap();
let successfully_written_config =
Config::write_config_to_disk_if_it_does_not_exist(config.to_string(true), &opts);
// if we successfully wrote the config to disk, it means two things:
// 1. It did not exist beforehand
// 2. The config folder is writeable
//
// If these two are true, we should launch the setup wizard, if even one of them is
// false, we should never launch it.
let should_launch_setup_wizard = successfully_written_config;
(
ClientToServerMsg::NewClient(
client_attributes,
Box::new(opts),
Box::new(opts.clone()),
Box::new(config.clone()),
Box::new(config_options.clone()),
Box::new(layout.unwrap()),
Box::new(config.plugins.clone()),
should_launch_setup_wizard,
),
ipc_pipe,
)
@ -522,6 +539,23 @@ pub fn start_client(
os_input.get_terminal_size_using_fd(0),
));
},
ClientInstruction::WriteConfigToDisk { config } => {
match Config::write_config_to_disk(config, &opts) {
Ok(written_config) => {
let _ = os_input
.send_to_server(ClientToServerMsg::ConfigWrittenToDisk(written_config));
},
Err(e) => {
let error_path = e
.as_ref()
.map(|p| p.display().to_string())
.unwrap_or_else(String::new);
log::error!("Failed to write config to disk: {}", error_path);
let _ = os_input
.send_to_server(ClientToServerMsg::FailedToWriteConfigToDisk(e));
},
}
},
_ => {},
}
}
@ -584,7 +618,6 @@ pub fn start_server_detached(
rounded_corners: config.ui.pane_frames.rounded_corners,
hide_session_name: config.ui.pane_frames.hide_session_name,
},
keybinds: config.keybinds.clone(),
};
let create_ipc_pipe = || -> std::path::PathBuf {
@ -602,14 +635,18 @@ pub fn start_server_detached(
let ipc_pipe = create_ipc_pipe();
spawn_server(&*ipc_pipe, opts.debug).unwrap();
let should_launch_setup_wizard = false; // no setup wizard when starting a detached
// server
(
ClientToServerMsg::NewClient(
client_attributes,
Box::new(opts),
Box::new(config.clone()),
Box::new(config_options.clone()),
Box::new(layout.unwrap()),
Box::new(config.plugins.clone()),
should_launch_setup_wizard,
),
ipc_pipe,
)

View file

@ -18,7 +18,7 @@ mod ui;
use background_jobs::{background_jobs_main, BackgroundJob};
use log::info;
use pty_writer::{pty_writer_main, PtyWriteInstruction};
use std::collections::{HashMap, HashSet};
use std::collections::{BTreeMap, HashMap, HashSet};
use std::{
path::PathBuf,
sync::{Arc, RwLock},
@ -28,7 +28,7 @@ use zellij_utils::envs;
use zellij_utils::nix::sys::stat::{umask, Mode};
use zellij_utils::pane_size::Size;
use wasmtime::{Config, Engine, Strategy};
use wasmtime::{Config as WasmtimeConfig, Engine, Strategy};
use crate::{
os_input_output::ServerOsApi,
@ -47,9 +47,10 @@ use zellij_utils::{
home::{default_layout_dir, get_default_data_dir},
input::{
command::{RunCommand, TerminalAction},
config::Config,
get_mode_info,
keybinds::Keybinds,
layout::Layout,
layout::{FloatingPaneLayout, Layout, PercentOrFixed, PluginAlias, Run, RunPluginOrAlias},
options::Options,
plugins::PluginAliases,
},
@ -64,9 +65,11 @@ pub enum ServerInstruction {
NewClient(
ClientAttributes,
Box<CliArgs>,
Box<Options>,
Box<Config>, // represents the saved config
Box<Options>, // represents the runtime configuration options
Box<Layout>,
Box<PluginAliases>,
bool, // should launch setup wizard
ClientId,
),
Render(Option<HashMap<ClientId, String>>),
@ -78,7 +81,8 @@ pub enum ServerInstruction {
DetachSession(Vec<ClientId>),
AttachClient(
ClientAttributes,
Options,
Config, // represents the saved config
Options, // represents the runtime configuration options
Option<usize>, // tab position to focus
Option<(u32, bool)>, // (pane_id, is_plugin) => pane_id to focus
ClientId,
@ -97,7 +101,13 @@ pub enum ServerInstruction {
DisconnectAllClientsExcept(ClientId),
ChangeMode(ClientId, InputMode),
ChangeModeForAllClients(InputMode),
Reconfigure(ClientId, String), // String -> stringified configuration
Reconfigure {
client_id: ClientId,
config: String,
write_config_to_disk: bool,
},
ConfigWrittenToDisk(ClientId, Config),
FailedToWriteConfigToDisk(ClientId, Option<PathBuf>), // Pathbuf - file we failed to write
}
impl From<&ServerInstruction> for ServerContext {
@ -129,7 +139,11 @@ impl From<&ServerInstruction> for ServerContext {
ServerInstruction::ChangeModeForAllClients(..) => {
ServerContext::ChangeModeForAllClients
},
ServerInstruction::Reconfigure(..) => ServerContext::Reconfigure,
ServerInstruction::Reconfigure { .. } => ServerContext::Reconfigure,
ServerInstruction::ConfigWrittenToDisk(..) => ServerContext::ConfigWrittenToDisk,
ServerInstruction::FailedToWriteConfigToDisk(..) => {
ServerContext::FailedToWriteConfigToDisk
},
}
}
}
@ -140,16 +154,83 @@ impl ErrorInstruction for ServerInstruction {
}
}
#[derive(Debug, Clone, Default)]
pub(crate) struct SessionConfiguration {
runtime_config: HashMap<ClientId, Config>, // if present, overrides the saved_config
saved_config: HashMap<ClientId, Config>, // config guaranteed to have been saved to disk
}
impl SessionConfiguration {
pub fn new_saved_config(&mut self, client_id: ClientId, mut new_saved_config: Config) {
self.saved_config
.insert(client_id, new_saved_config.clone());
if let Some(runtime_config) = self.runtime_config.get_mut(&client_id) {
match new_saved_config.merge(runtime_config.clone()) {
Ok(_) => {
*runtime_config = new_saved_config;
},
Err(e) => {
log::error!("Failed to update runtime config: {}", e);
},
}
}
// TODO: handle change by propagating to all the relevant places
}
pub fn set_client_saved_configuration(&mut self, client_id: ClientId, client_config: Config) {
self.saved_config.insert(client_id, client_config);
}
pub fn set_client_runtime_configuration(&mut self, client_id: ClientId, client_config: Config) {
self.runtime_config.insert(client_id, client_config);
}
pub fn get_client_keybinds(&self, client_id: &ClientId) -> Keybinds {
self.runtime_config
.get(client_id)
.or_else(|| self.saved_config.get(client_id))
.map(|c| c.keybinds.clone())
.unwrap_or_default()
}
pub fn get_client_configuration(&self, client_id: &ClientId) -> Config {
self.runtime_config
.get(client_id)
.or_else(|| self.saved_config.get(client_id))
.cloned()
.unwrap_or_default()
}
pub fn reconfigure_runtime_config(
&mut self,
client_id: &ClientId,
stringified_config: String,
) -> (Option<Config>, bool) {
// bool is whether the config changed
let mut full_reconfigured_config = None;
let mut config_changed = false;
let current_client_configuration = self.get_client_configuration(client_id);
match Config::from_kdl(
&stringified_config,
Some(current_client_configuration.clone()),
) {
Ok(new_config) => {
config_changed = current_client_configuration != new_config;
full_reconfigured_config = Some(new_config.clone());
self.runtime_config.insert(*client_id, new_config);
},
Err(e) => {
log::error!("Failed to reconfigure runtime config: {}", e);
},
}
(full_reconfigured_config, config_changed)
}
}
pub(crate) struct SessionMetaData {
pub senders: ThreadSenders,
pub capabilities: PluginCapabilities,
pub client_attributes: ClientAttributes,
pub default_shell: Option<TerminalAction>,
pub layout: Box<Layout>,
pub config_options: Box<Options>,
pub client_keybinds: HashMap<ClientId, Keybinds>,
pub client_input_modes: HashMap<ClientId, InputMode>,
pub default_mode: HashMap<ClientId, InputMode>,
pub current_input_modes: HashMap<ClientId, InputMode>,
pub session_configuration: SessionConfiguration,
screen_thread: Option<thread::JoinHandle<()>>,
pty_thread: Option<thread::JoinHandle<()>>,
plugin_thread: Option<thread::JoinHandle<()>>,
@ -158,53 +239,22 @@ pub(crate) struct SessionMetaData {
}
impl SessionMetaData {
pub fn set_client_keybinds(&mut self, client_id: ClientId, keybinds: Keybinds) {
self.client_keybinds.insert(client_id, keybinds);
self.client_input_modes.insert(
client_id,
self.config_options.default_mode.unwrap_or_default(),
);
}
pub fn get_client_keybinds_and_mode(
&self,
client_id: &ClientId,
) -> Option<(&Keybinds, &InputMode)> {
match (
self.client_keybinds.get(client_id),
self.client_input_modes.get(client_id),
) {
(Some(client_keybinds), Some(client_input_mode)) => {
Some((client_keybinds, client_input_mode))
},
) -> Option<(Keybinds, &InputMode)> {
let client_keybinds = self.session_configuration.get_client_keybinds(client_id);
match self.current_input_modes.get(client_id) {
Some(client_input_mode) => Some((client_keybinds, client_input_mode)),
_ => None,
}
}
pub fn change_mode_for_all_clients(&mut self, input_mode: InputMode) {
let all_clients: Vec<ClientId> = self.client_input_modes.keys().copied().collect();
let all_clients: Vec<ClientId> = self.current_input_modes.keys().copied().collect();
for client_id in all_clients {
self.client_input_modes.insert(client_id, input_mode);
self.current_input_modes.insert(client_id, input_mode);
}
}
pub fn rebind_keys(&mut self, client_id: ClientId, new_keybinds: String) -> Option<Keybinds> {
if let Some(current_keybinds) = self.client_keybinds.get_mut(&client_id) {
match Keybinds::from_string(
new_keybinds,
current_keybinds.clone(),
&self.config_options,
) {
Ok(new_keybinds) => {
*current_keybinds = new_keybinds.clone();
return Some(new_keybinds);
},
Err(e) => {
log::error!("Failed to parse keybindings: {}", e);
},
}
} else {
log::error!("Failed to bind keys for client: {client_id}");
}
None
}
}
impl Drop for SessionMetaData {
@ -426,44 +476,55 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
err_ctx.add_call(ContextType::IPCServer((&instruction).into()));
match instruction {
ServerInstruction::NewClient(
// TODO: rename to FirstClientConnected?
client_attributes,
opts,
config_options,
config,
runtime_config_options,
layout,
plugin_aliases,
should_launch_setup_wizard,
client_id,
) => {
let session = init_session(
let mut session = init_session(
os_input.clone(),
to_server.clone(),
client_attributes.clone(),
SessionOptions {
opts,
layout: layout.clone(),
config_options: config_options.clone(),
config_options: runtime_config_options.clone(),
},
*config.clone(),
plugin_aliases,
);
let mut runtime_configuration = config.clone();
runtime_configuration.options = *runtime_config_options.clone();
session
.session_configuration
.set_client_saved_configuration(client_id, *config.clone());
session
.session_configuration
.set_client_runtime_configuration(client_id, *runtime_configuration);
let default_input_mode = runtime_config_options.default_mode.unwrap_or_default();
session
.current_input_modes
.insert(client_id, default_input_mode);
*session_data.write().unwrap() = Some(session);
session_data
.write()
.unwrap()
.as_mut()
.unwrap()
.set_client_keybinds(client_id, client_attributes.keybinds.clone());
session_state
.write()
.unwrap()
.set_client_size(client_id, client_attributes.size);
let default_shell = config_options.default_shell.map(|shell| {
let default_shell = runtime_config_options.default_shell.map(|shell| {
TerminalAction::RunCommand(RunCommand {
command: shell,
cwd: config_options.default_cwd.clone(),
cwd: config.options.default_cwd.clone(),
..Default::default()
})
});
let cwd = config_options.default_cwd;
let cwd = runtime_config_options.default_cwd;
let spawn_tabs = |tab_layout, floating_panes_layout, tab_name, swap_layouts| {
session_data
@ -511,9 +572,17 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.unwrap();
}
} else {
let mut floating_panes =
layout.template.map(|t| t.1).clone().unwrap_or_default();
if should_launch_setup_wizard {
// we only do this here (and only once) because otherwise it will be
// intrusive
let setup_wizard = setup_wizard_floating_pane();
floating_panes.push(setup_wizard);
}
spawn_tabs(
None,
layout.template.map(|t| t.1).clone().unwrap_or_default(),
floating_panes,
None,
(
layout.swap_tiled_layouts.clone(),
@ -532,14 +601,29 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
},
ServerInstruction::AttachClient(
attrs,
options,
config,
runtime_config_options,
tab_position_to_focus,
pane_id_to_focus,
client_id,
) => {
let mut rlock = session_data.write().unwrap();
let session_data = rlock.as_mut().unwrap();
session_data.set_client_keybinds(client_id, attrs.keybinds.clone());
let mut runtime_configuration = config.clone();
runtime_configuration.options = runtime_config_options.clone();
session_data
.session_configuration
.set_client_saved_configuration(client_id, config.clone());
session_data
.session_configuration
.set_client_runtime_configuration(client_id, runtime_configuration);
let default_input_mode = config.options.default_mode.unwrap_or_default();
session_data
.current_input_modes
.insert(client_id, default_input_mode);
session_state
.write()
.unwrap()
@ -565,15 +649,14 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.senders
.send_to_plugin(PluginInstruction::AddClient(client_id))
.unwrap();
let default_mode = options.default_mode.unwrap_or_default();
let default_mode = config.options.default_mode.unwrap_or_default();
let mode_info = get_mode_info(
default_mode,
&attrs,
session_data.capabilities,
session_data
.client_keybinds
.get(&client_id)
.unwrap_or(&session_data.client_attributes.keybinds),
&session_data
.session_configuration
.get_client_keybinds(&client_id),
Some(default_mode),
);
session_data
@ -853,7 +936,11 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.read()
.unwrap()
.as_ref()
.and_then(|c| c.config_options.layout_dir.clone())
.unwrap()
.session_configuration
.get_client_configuration(&client_id)
.options
.layout_dir
.or_else(|| default_layout_dir());
if let Some(layout_dir) = layout_dir {
connect_to_session.apply_layout_dir(&layout_dir);
@ -904,7 +991,7 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.unwrap()
.as_mut()
.unwrap()
.client_input_modes
.current_input_modes
.insert(client_id, input_mode);
},
ServerInstruction::ChangeModeForAllClients(input_mode) => {
@ -915,33 +1002,33 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.unwrap()
.change_mode_for_all_clients(input_mode);
},
ServerInstruction::Reconfigure(client_id, new_config) => {
let mut new_default_mode = None;
match Options::from_string(&new_config) {
Ok(mut new_config_options) => {
if let Some(default_mode) = new_config_options.default_mode.take() {
new_default_mode = Some(default_mode);
session_data
ServerInstruction::Reconfigure {
client_id,
config,
write_config_to_disk,
} => {
let (new_config, runtime_config_changed) = session_data
.write()
.unwrap()
.as_mut()
.unwrap()
.default_mode
.insert(client_id, default_mode);
}
},
Err(e) => {
log::error!("Failed to parse config: {}", e);
.session_configuration
.reconfigure_runtime_config(&client_id, config);
if let Some(new_config) = new_config {
if write_config_to_disk {
let clear_defaults = true;
send_to_client!(
client_id,
os_input,
ServerToClientMsg::WriteConfigToDisk {
config: new_config.to_string(clear_defaults)
},
session_state
);
}
let new_keybinds = session_data
.write()
.unwrap()
.as_mut()
.unwrap()
.rebind_keys(client_id, new_config)
.clone();
if runtime_config_changed {
session_data
.write()
.unwrap()
@ -950,8 +1037,8 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.senders
.send_to_screen(ScreenInstruction::Reconfigure {
client_id,
keybinds: new_keybinds.clone(),
default_mode: new_default_mode,
keybinds: Some(new_config.keybinds.clone()),
default_mode: new_config.options.default_mode,
})
.unwrap();
session_data
@ -962,10 +1049,31 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.senders
.send_to_plugin(PluginInstruction::Reconfigure {
client_id,
keybinds: new_keybinds,
default_mode: new_default_mode,
keybinds: Some(new_config.keybinds),
default_mode: new_config.options.default_mode,
})
.unwrap();
}
}
},
ServerInstruction::ConfigWrittenToDisk(client_id, new_config) => {
session_data
.write()
.unwrap()
.as_mut()
.unwrap()
.session_configuration
.new_saved_config(client_id, new_config);
},
ServerInstruction::FailedToWriteConfigToDisk(client_id, file_path) => {
session_data
.write()
.unwrap()
.as_ref()
.unwrap()
.senders
.send_to_plugin(PluginInstruction::FailedToWriteConfigToDisk { file_path })
.unwrap();
},
}
}
@ -987,6 +1095,7 @@ fn init_session(
to_server: SenderWithContext<ServerInstruction>,
client_attributes: ClientAttributes,
options: SessionOptions,
mut config: Config,
plugin_aliases: Box<PluginAliases>,
) -> SessionMetaData {
let SessionOptions {
@ -994,6 +1103,7 @@ fn init_session(
config_options,
layout,
} = options;
config.options = config.options.merge(*config_options.clone());
let _ = SCROLL_BUFFER_SIZE.set(
config_options
@ -1043,6 +1153,7 @@ fn init_session(
.unwrap_or_else(|| get_default_shell());
let default_mode = config_options.default_mode.unwrap_or_default();
let default_keybinds = config.keybinds.clone();
let pty_thread = thread::Builder::new()
.name("pty".to_string())
@ -1085,13 +1196,12 @@ fn init_session(
let client_attributes_clone = client_attributes.clone();
let debug = opts.debug;
let layout = layout.clone();
let config_options = config_options.clone();
move || {
screen_thread_main(
screen_bus,
max_panes,
client_attributes_clone,
config_options,
config,
debug,
layout,
)
@ -1135,6 +1245,7 @@ fn init_session(
default_shell,
plugin_aliases,
default_mode,
default_keybinds,
)
.fatal()
}
@ -1196,26 +1307,35 @@ fn init_session(
default_shell,
client_attributes,
layout,
config_options: config_options.clone(),
client_keybinds: HashMap::new(),
client_input_modes: HashMap::new(),
session_configuration: Default::default(),
current_input_modes: HashMap::new(),
screen_thread: Some(screen_thread),
pty_thread: Some(pty_thread),
plugin_thread: Some(plugin_thread),
pty_writer_thread: Some(pty_writer_thread),
background_jobs_thread: Some(background_jobs_thread),
default_mode: HashMap::new(),
}
}
fn setup_wizard_floating_pane() -> FloatingPaneLayout {
let mut setup_wizard_pane = FloatingPaneLayout::new();
let configuration = BTreeMap::from_iter([("is_setup_wizard".to_owned(), "true".to_owned())]);
setup_wizard_pane.run = Some(Run::Plugin(RunPluginOrAlias::Alias(PluginAlias::new(
"configuration",
&Some(configuration),
None,
))));
setup_wizard_pane
}
#[cfg(not(feature = "singlepass"))]
fn get_engine() -> Engine {
log::info!("Compiling plugins using Cranelift");
Engine::new(Config::new().strategy(Strategy::Cranelift)).unwrap()
Engine::new(WasmtimeConfig::new().strategy(Strategy::Cranelift)).unwrap()
}
#[cfg(feature = "singlepass")]
fn get_engine() -> Engine {
log::info!("Compiling plugins using Singlepass");
Engine::new(Config::new().strategy(Strategy::Winch)).unwrap()
Engine::new(WasmtimeConfig::new().strategy(Strategy::Winch)).unwrap()
}

View file

@ -148,6 +148,9 @@ pub enum PluginInstruction {
keybinds: Option<Keybinds>,
default_mode: Option<InputMode>,
},
FailedToWriteConfigToDisk {
file_path: Option<PathBuf>,
},
WatchFilesystem,
Exit,
}
@ -189,6 +192,9 @@ impl From<&PluginInstruction> for PluginContext {
PluginInstruction::KeybindPipe { .. } => PluginContext::KeybindPipe,
PluginInstruction::DumpLayoutToPlugin(..) => PluginContext::DumpLayoutToPlugin,
PluginInstruction::Reconfigure { .. } => PluginContext::Reconfigure,
PluginInstruction::FailedToWriteConfigToDisk { .. } => {
PluginContext::FailedToWriteConfigToDisk
},
}
}
}
@ -206,6 +212,7 @@ pub(crate) fn plugin_thread_main(
default_shell: Option<TerminalAction>,
plugin_aliases: Box<PluginAliases>,
default_mode: InputMode,
default_keybinds: Keybinds,
) -> Result<()> {
info!("Wasm main thread starts");
let plugin_dir = data_dir.join("plugins/");
@ -228,6 +235,7 @@ pub(crate) fn plugin_thread_main(
layout.clone(),
layout_dir,
default_mode,
default_keybinds,
);
loop {
@ -764,6 +772,16 @@ pub(crate) fn plugin_thread_main(
.reconfigure(client_id, keybinds, default_mode)
.non_fatal();
},
PluginInstruction::FailedToWriteConfigToDisk { file_path } => {
let updates = vec![(
None,
None,
Event::FailedToWriteConfigToDisk(file_path.map(|f| f.display().to_string())),
)];
wasm_bridge
.update_plugins(updates, shutdown_send.clone())
.non_fatal();
},
PluginInstruction::WatchFilesystem => {
wasm_bridge.start_fs_watcher_if_not_started();
},

View file

@ -70,7 +70,7 @@ pub struct PluginLoader<'a> {
default_layout: Box<Layout>,
layout_dir: Option<PathBuf>,
default_mode: InputMode,
keybinds: Option<Keybinds>,
keybinds: Keybinds,
}
impl<'a> PluginLoader<'a> {
@ -90,7 +90,7 @@ impl<'a> PluginLoader<'a> {
default_shell: Option<TerminalAction>,
default_layout: Box<Layout>,
layout_dir: Option<PathBuf>,
default_mode: InputMode,
base_modes: &HashMap<ClientId, InputMode>,
keybinds: &HashMap<ClientId, Keybinds>,
) -> Result<()> {
let err_context = || format!("failed to reload plugin {plugin_id} from memory");
@ -100,7 +100,11 @@ impl<'a> PluginLoader<'a> {
return Err(anyhow!("No connected clients, cannot reload plugin"));
}
let first_client_id = connected_clients.remove(0);
let keybinds = keybinds.get(&first_client_id).cloned();
let keybinds = keybinds.get(&first_client_id).cloned().unwrap_or_default();
let default_mode = base_modes
.get(&first_client_id)
.cloned()
.unwrap_or_default();
let mut plugin_loader = PluginLoader::new_from_existing_plugin_attributes(
&plugin_cache,
@ -157,7 +161,7 @@ impl<'a> PluginLoader<'a> {
skip_cache: bool,
layout_dir: Option<PathBuf>,
default_mode: InputMode,
keybinds: Option<Keybinds>,
keybinds: Keybinds,
) -> Result<()> {
let err_context = || format!("failed to start plugin {plugin_id} for client {client_id}");
let mut plugin_loader = PluginLoader::new(
@ -233,7 +237,7 @@ impl<'a> PluginLoader<'a> {
default_layout: Box<Layout>,
layout_dir: Option<PathBuf>,
default_mode: InputMode,
keybinds: Option<Keybinds>,
keybinds: Keybinds,
) -> Result<()> {
let mut new_plugins = HashSet::new();
for plugin_id in plugin_map.lock().unwrap().plugin_ids() {
@ -286,7 +290,7 @@ impl<'a> PluginLoader<'a> {
default_shell: Option<TerminalAction>,
default_layout: Box<Layout>,
layout_dir: Option<PathBuf>,
default_mode: InputMode,
base_modes: &HashMap<ClientId, InputMode>,
keybinds: &HashMap<ClientId, Keybinds>,
) -> Result<()> {
let err_context = || format!("failed to reload plugin id {plugin_id}");
@ -297,7 +301,11 @@ impl<'a> PluginLoader<'a> {
return Err(anyhow!("No connected clients, cannot reload plugin"));
}
let first_client_id = connected_clients.remove(0);
let keybinds = keybinds.get(&first_client_id).cloned();
let keybinds = keybinds.get(&first_client_id).cloned().unwrap_or_default();
let default_mode = base_modes
.get(&first_client_id)
.cloned()
.unwrap_or_default();
let mut plugin_loader = PluginLoader::new_from_existing_plugin_attributes(
&plugin_cache,
@ -350,7 +358,7 @@ impl<'a> PluginLoader<'a> {
default_layout: Box<Layout>,
layout_dir: Option<PathBuf>,
default_mode: InputMode,
keybinds: Option<Keybinds>,
keybinds: Keybinds,
) -> Result<Self> {
let plugin_own_data_dir = ZELLIJ_SESSION_CACHE_DIR
.join(Url::from(&plugin.location).to_string())
@ -399,7 +407,7 @@ impl<'a> PluginLoader<'a> {
default_layout: Box<Layout>,
layout_dir: Option<PathBuf>,
default_mode: InputMode,
keybinds: Option<Keybinds>,
keybinds: Keybinds,
) -> Result<Self> {
let err_context = || "Failed to find existing plugin";
let (running_plugin, _subscriptions, _workers) = {
@ -455,7 +463,7 @@ impl<'a> PluginLoader<'a> {
default_layout: Box<Layout>,
layout_dir: Option<PathBuf>,
default_mode: InputMode,
keybinds: Option<Keybinds>,
keybinds: Keybinds,
) -> Result<Self> {
let err_context = || "Failed to find existing plugin";
let running_plugin = {
@ -851,11 +859,7 @@ impl<'a> PluginLoader<'a> {
layout_dir: self.layout_dir.clone(),
default_mode: self.default_mode.clone(),
subscriptions: Arc::new(Mutex::new(HashSet::new())),
keybinds: self
.keybinds
.as_ref()
.unwrap_or_else(|| &self.client_attributes.keybinds)
.clone(),
keybinds: self.keybinds.clone(),
stdin_pipe,
stdout_pipe,
};

View file

@ -11,6 +11,7 @@ use zellij_utils::data::{
PluginCapabilities,
};
use zellij_utils::errors::ErrorContext;
use zellij_utils::input::keybinds::Keybinds;
use zellij_utils::input::layout::{
Layout, PluginAlias, PluginUserConfiguration, RunPlugin, RunPluginLocation, RunPluginOrAlias,
};
@ -58,6 +59,35 @@ macro_rules! log_actions_in_thread {
};
}
macro_rules! log_actions_in_thread_struct {
( $arc_mutex_log:expr, $exit_event:path, $receiver:expr, $exit_after_count:expr ) => {
std::thread::Builder::new()
.name("logger thread".to_string())
.spawn({
let log = $arc_mutex_log.clone();
let mut exit_event_count = 0;
move || loop {
let (event, _err_ctx) = $receiver
.recv()
.expect("failed to receive event on channel");
match event {
$exit_event { .. } => {
exit_event_count += 1;
log.lock().unwrap().push(event);
if exit_event_count == $exit_after_count {
break;
}
},
_ => {
log.lock().unwrap().push(event);
},
}
}
})
.unwrap()
};
}
macro_rules! grant_permissions_and_log_actions_in_thread {
( $arc_mutex_log:expr, $exit_event:path, $receiver:expr, $exit_after_count:expr, $permission_type:expr, $cache_path:expr, $plugin_thread_sender:expr, $client_id:expr ) => {
std::thread::Builder::new()
@ -282,6 +312,7 @@ fn create_plugin_thread(
default_shell_action,
Box::new(plugin_aliases),
InputMode::Normal,
Keybinds::default(),
)
.expect("TEST")
})
@ -362,6 +393,7 @@ fn create_plugin_thread_with_server_receiver(
default_shell_action,
Box::new(PluginAliases::default()),
InputMode::Normal,
Keybinds::default(),
)
.expect("TEST");
})
@ -448,6 +480,7 @@ fn create_plugin_thread_with_pty_receiver(
default_shell_action,
Box::new(PluginAliases::default()),
InputMode::Normal,
Keybinds::default(),
)
.expect("TEST")
})
@ -529,6 +562,7 @@ fn create_plugin_thread_with_background_jobs_receiver(
default_shell_action,
Box::new(PluginAliases::default()),
InputMode::Normal,
Keybinds::default(),
)
.expect("TEST")
})
@ -6525,7 +6559,7 @@ pub fn reconfigure_plugin_command() {
client_id
);
let received_server_instruction = Arc::new(Mutex::new(vec![]));
let server_thread = log_actions_in_thread!(
let server_thread = log_actions_in_thread_struct!(
received_server_instruction,
ServerInstruction::Reconfigure,
server_receiver,
@ -6561,7 +6595,7 @@ pub fn reconfigure_plugin_command() {
.iter()
.rev()
.find_map(|i| {
if let ServerInstruction::Reconfigure(..) = i {
if let ServerInstruction::Reconfigure { .. } = i {
Some(i.clone())
} else {
None

View file

@ -1,11 +1,12 @@
---
source: zellij-server/src/plugins/./unit/plugin_tests.rs
assertion_line: 6571
assertion_line: 6605
expression: "format!(\"{:#?}\", reconfigure_event)"
---
Some(
Reconfigure(
1,
"\n keybinds {\n locked {\n bind \"a\" { NewTab; }\n }\n }\n ",
),
Reconfigure {
client_id: 1,
config: "\n keybinds {\n locked {\n bind \"a\" { NewTab; }\n }\n }\n ",
write_config_to_disk: true,
},
)

View file

@ -104,7 +104,9 @@ pub struct WasmBridge {
pending_pipes: PendingPipes,
layout_dir: Option<PathBuf>,
default_mode: InputMode,
default_keybinds: Keybinds,
keybinds: HashMap<ClientId, Keybinds>,
base_modes: HashMap<ClientId, InputMode>,
}
impl WasmBridge {
@ -120,6 +122,7 @@ impl WasmBridge {
default_layout: Box<Layout>,
layout_dir: Option<PathBuf>,
default_mode: InputMode,
default_keybinds: Keybinds,
) -> Self {
let plugin_map = Arc::new(Mutex::new(PluginMap::default()));
let connected_clients: Arc<Mutex<Vec<ClientId>>> = Arc::new(Mutex::new(vec![]));
@ -151,7 +154,9 @@ impl WasmBridge {
pending_pipes: Default::default(),
layout_dir,
default_mode,
default_keybinds,
keybinds: HashMap::new(),
base_modes: HashMap::new(),
}
}
pub fn load_plugin(
@ -208,8 +213,16 @@ impl WasmBridge {
let default_shell = self.default_shell.clone();
let default_layout = self.default_layout.clone();
let layout_dir = self.layout_dir.clone();
let default_mode = self.default_mode;
let keybinds = self.keybinds.get(&client_id).cloned();
let default_mode = self
.base_modes
.get(&client_id)
.copied()
.unwrap_or(self.default_mode);
let keybinds = self
.keybinds
.get(&client_id)
.cloned()
.unwrap_or_else(|| self.default_keybinds.clone());
async move {
let _ = senders.send_to_background_jobs(
BackgroundJob::AnimatePluginLoading(plugin_id),
@ -350,7 +363,7 @@ impl WasmBridge {
let default_shell = self.default_shell.clone();
let default_layout = self.default_layout.clone();
let layout_dir = self.layout_dir.clone();
let default_mode = self.default_mode;
let base_modes = self.base_modes.clone();
let keybinds = self.keybinds.clone();
async move {
match PluginLoader::reload_plugin(
@ -369,7 +382,7 @@ impl WasmBridge {
default_shell.clone(),
default_layout.clone(),
layout_dir.clone(),
default_mode,
&base_modes,
&keybinds,
) {
Ok(_) => {
@ -396,7 +409,7 @@ impl WasmBridge {
default_shell.clone(),
default_layout.clone(),
layout_dir.clone(),
default_mode,
&base_modes,
&keybinds,
) {
Ok(_) => handle_plugin_successful_loading(&senders, *plugin_id),
@ -451,7 +464,10 @@ impl WasmBridge {
self.default_layout.clone(),
self.layout_dir.clone(),
self.default_mode,
self.keybinds.get(&client_id).cloned(),
self.keybinds
.get(&client_id)
.cloned()
.unwrap_or_else(|| self.default_keybinds.clone()),
) {
Ok(_) => {
let _ = self
@ -827,7 +843,7 @@ impl WasmBridge {
})
.collect();
if let Some(default_mode) = default_mode.as_ref() {
self.default_mode = *default_mode;
self.base_modes.insert(client_id, *default_mode);
}
if let Some(keybinds) = keybinds.as_ref() {
self.keybinds.insert(client_id, keybinds.clone());
@ -1332,6 +1348,7 @@ fn check_event_permission(
| Event::PaneClosed(..)
| Event::EditPaneOpened(..)
| Event::EditPaneExited(..)
| Event::FailedToWriteConfigToDisk(..)
| Event::CommandPaneReRun(..)
| Event::InputReceived => PermissionType::ReadApplicationState,
_ => return (PermissionStatus::Granted, None),

View file

@ -257,7 +257,9 @@ fn host_run_plugin_command(caller: Caller<'_, PluginEnv>) {
PluginCommand::WatchFilesystem => watch_filesystem(env),
PluginCommand::DumpSessionLayout => dump_session_layout(env),
PluginCommand::CloseSelf => close_self(env),
PluginCommand::Reconfigure(new_config) => reconfigure(env, new_config)?,
PluginCommand::Reconfigure(new_config, write_config_to_disk) => {
reconfigure(env, new_config, write_config_to_disk)?
},
PluginCommand::HidePaneWithId(pane_id) => {
hide_pane_with_id(env, pane_id.into())?
},
@ -894,11 +896,15 @@ fn close_self(env: &PluginEnv) {
.non_fatal();
}
fn reconfigure(env: &PluginEnv, new_config: String) -> Result<()> {
fn reconfigure(env: &PluginEnv, new_config: String, write_config_to_disk: bool) -> Result<()> {
let err_context = || "Failed to reconfigure";
let client_id = env.client_id;
env.senders
.send_to_server(ServerInstruction::Reconfigure(client_id, new_config))
.send_to_server(ServerInstruction::Reconfigure {
client_id,
config: new_config,
write_config_to_disk,
})
.with_context(err_context)?;
Ok(())
}

View file

@ -1045,19 +1045,13 @@ pub(crate) fn route_thread_main(
rlocked_sessions.default_shell.clone(),
rlocked_sessions.layout.clone(),
Some(&mut seen_cli_pipes),
keybinds.clone(),
rlocked_sessions
.client_keybinds
.get(&client_id)
.unwrap_or(
&rlocked_sessions
.client_attributes
.keybinds,
)
.clone(),
rlocked_sessions
.session_configuration
.get_client_configuration(&client_id)
.options
.default_mode
.get(&client_id)
.unwrap_or(&InputMode::Normal)
.unwrap_or(InputMode::Normal)
.clone(),
)? {
should_break = true;
@ -1084,14 +1078,15 @@ pub(crate) fn route_thread_main(
rlocked_sessions.layout.clone(),
Some(&mut seen_cli_pipes),
rlocked_sessions
.client_keybinds
.get(&client_id)
.unwrap_or(&rlocked_sessions.client_attributes.keybinds)
.session_configuration
.get_client_keybinds(&client_id)
.clone(),
rlocked_sessions
.session_configuration
.get_client_configuration(&client_id)
.options
.default_mode
.get(&client_id)
.unwrap_or(&InputMode::Normal)
.unwrap_or(InputMode::Normal)
.clone(),
)? {
should_break = true;
@ -1164,16 +1159,20 @@ pub(crate) fn route_thread_main(
ClientToServerMsg::NewClient(
client_attributes,
cli_args,
opts,
config,
runtime_config_options,
layout,
plugin_aliases,
should_launch_setup_wizard,
) => {
let new_client_instruction = ServerInstruction::NewClient(
client_attributes,
cli_args,
opts,
config,
runtime_config_options,
layout,
plugin_aliases,
should_launch_setup_wizard,
client_id,
);
to_server
@ -1182,13 +1181,15 @@ pub(crate) fn route_thread_main(
},
ClientToServerMsg::AttachClient(
client_attributes,
opts,
config,
runtime_config_options,
tab_position_to_focus,
pane_id_to_focus,
) => {
let attach_client_instruction = ServerInstruction::AttachClient(
client_attributes,
opts,
config,
runtime_config_options,
tab_position_to_focus,
pane_id_to_focus,
client_id,
@ -1219,6 +1220,16 @@ pub(crate) fn route_thread_main(
ClientToServerMsg::ListClients => {
let _ = to_server.send(ServerInstruction::ActiveClients(client_id));
},
ClientToServerMsg::ConfigWrittenToDisk(config) => {
let _ = to_server
.send(ServerInstruction::ConfigWrittenToDisk(client_id, config));
},
ClientToServerMsg::FailedToWriteConfigToDisk(failed_path) => {
let _ = to_server.send(ServerInstruction::FailedToWriteConfigToDisk(
client_id,
failed_path,
));
},
}
Ok(should_break)
};

View file

@ -13,6 +13,7 @@ use zellij_utils::data::{
};
use zellij_utils::errors::prelude::*;
use zellij_utils::input::command::RunCommand;
use zellij_utils::input::config::Config;
use zellij_utils::input::keybinds::Keybinds;
use zellij_utils::input::options::Clipboard;
use zellij_utils::pane_size::{Size, SizeInPixels};
@ -2361,10 +2362,11 @@ pub(crate) fn screen_thread_main(
bus: Bus<ScreenInstruction>,
max_panes: Option<usize>,
client_attributes: ClientAttributes,
config_options: Box<Options>,
config: Config,
debug: bool,
default_layout: Box<Layout>,
) -> Result<()> {
let config_options = config.options;
let arrow_fonts = !config_options.simplified_ui.unwrap_or_default();
let draw_pane_frames = config_options.pane_frames.unwrap_or(true);
let auto_layout = config_options.auto_layout.unwrap_or(true);
@ -2403,7 +2405,7 @@ pub(crate) fn screen_thread_main(
// ¯\_(ツ)_/¯
arrow_fonts: !arrow_fonts,
},
&client_attributes.keybinds,
&config.keybinds,
config_options.default_mode,
),
draw_pane_frames,

View file

@ -14,6 +14,7 @@ use zellij_utils::data::{Event, Resize, Style};
use zellij_utils::errors::{prelude::*, ErrorContext};
use zellij_utils::input::actions::Action;
use zellij_utils::input::command::{RunCommand, TerminalAction};
use zellij_utils::input::config::Config;
use zellij_utils::input::layout::{
FloatingPaneLayout, Layout, PluginAlias, PluginUserConfiguration, Run, RunPlugin,
RunPluginLocation, RunPluginOrAlias, SplitDirection, SplitSize, TiledPaneLayout,
@ -114,12 +115,20 @@ fn send_cli_action_to_server(
let get_current_dir = || PathBuf::from(".");
let actions = Action::actions_from_cli(cli_action, Box::new(get_current_dir), None).unwrap();
let senders = session_metadata.senders.clone();
let client_keybinds = session_metadata.client_keybinds.clone();
let default_mode = session_metadata.default_mode.clone();
let capabilities = PluginCapabilities::default();
let client_attributes = ClientAttributes::default();
let default_shell = None;
let default_layout = Box::new(Layout::default());
let default_mode = session_metadata
.session_configuration
.get_client_configuration(&client_id)
.options
.default_mode
.unwrap_or(InputMode::Normal);
let client_keybinds = session_metadata
.session_configuration
.get_client_keybinds(&client_id)
.clone();
for action in actions {
route_action(
action,
@ -131,14 +140,8 @@ fn send_cli_action_to_server(
default_shell.clone(),
default_layout.clone(),
None,
client_keybinds
.get(&client_id)
.unwrap_or(&session_metadata.client_attributes.keybinds)
.clone(),
default_mode
.get(&client_id)
.unwrap_or(&InputMode::Normal)
.clone(),
client_keybinds.clone(),
default_mode,
)
.unwrap();
}
@ -307,6 +310,7 @@ struct MockScreen {
pub client_attributes: ClientAttributes,
pub config_options: Options,
pub session_metadata: SessionMetaData,
pub config: Config,
last_opened_tab_index: Option<usize>,
}
@ -316,7 +320,7 @@ impl MockScreen {
initial_layout: Option<TiledPaneLayout>,
initial_floating_panes_layout: Vec<FloatingPaneLayout>,
) -> std::thread::JoinHandle<()> {
let config_options = self.config_options.clone();
let config = self.config.clone();
let client_attributes = self.client_attributes.clone();
let screen_bus = Bus::new(
vec![self.screen_receiver.take().unwrap()],
@ -338,7 +342,7 @@ impl MockScreen {
screen_bus,
None,
client_attributes,
Box::new(config_options),
config,
debug,
Box::new(Layout::default()),
)
@ -391,7 +395,7 @@ impl MockScreen {
initial_layout: Option<TiledPaneLayout>,
initial_floating_panes_layout: Vec<FloatingPaneLayout>,
) -> std::thread::JoinHandle<()> {
let config_options = self.config_options.clone();
let config = self.config.clone();
let client_attributes = self.client_attributes.clone();
let screen_bus = Bus::new(
vec![self.screen_receiver.take().unwrap()],
@ -413,7 +417,7 @@ impl MockScreen {
screen_bus,
None,
client_attributes,
Box::new(config_options),
config,
debug,
Box::new(Layout::default()),
)
@ -522,11 +526,9 @@ impl MockScreen {
plugin_thread: None,
pty_writer_thread: None,
background_jobs_thread: None,
config_options: Default::default(),
session_configuration: self.session_metadata.session_configuration.clone(),
layout,
client_input_modes: HashMap::new(),
client_keybinds: HashMap::new(),
default_mode: self.session_metadata.default_mode.clone(),
current_input_modes: self.session_metadata.current_input_modes.clone(),
}
}
}
@ -582,11 +584,9 @@ impl MockScreen {
plugin_thread: None,
pty_writer_thread: None,
background_jobs_thread: None,
config_options: Default::default(),
layout,
client_input_modes: HashMap::new(),
client_keybinds: HashMap::new(),
default_mode: HashMap::new(),
session_configuration: Default::default(),
current_input_modes: HashMap::new(),
};
let os_input = FakeInputOutput::default();
@ -611,6 +611,7 @@ impl MockScreen {
config_options,
session_metadata,
last_opened_tab_index: None,
config: Config::default(),
}
}
}

View file

@ -842,8 +842,8 @@ pub fn dump_session_layout() {
}
/// Rebind keys for the current user
pub fn reconfigure(new_config: String) {
let plugin_command = PluginCommand::Reconfigure(new_config);
pub fn reconfigure(new_config: String, save_configuration_file: bool) {
let plugin_command = PluginCommand::Reconfigure(new_config, save_configuration_file);
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
object_to_stdout(&protobuf_plugin_command.encode_to_vec());
unsafe { host_run_plugin_command() };

View file

@ -11,7 +11,7 @@ pub struct Event {
pub name: i32,
#[prost(
oneof = "event::Payload",
tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21"
tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22"
)]
pub payload: ::core::option::Option<event::Payload>,
}
@ -60,10 +60,18 @@ pub mod event {
EditPaneExitedPayload(super::EditPaneExitedPayload),
#[prost(message, tag = "21")]
CommandPaneRerunPayload(super::CommandPaneReRunPayload),
#[prost(message, tag = "22")]
FailedToWriteConfigToDiskPayload(super::FailedToWriteConfigToDiskPayload),
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct FailedToWriteConfigToDiskPayload {
#[prost(string, optional, tag = "1")]
pub file_path: ::core::option::Option<::prost::alloc::string::String>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct CommandPaneReRunPayload {
#[prost(uint32, tag = "1")]
pub terminal_pane_id: u32,
@ -426,6 +434,7 @@ pub enum EventType {
EditPaneOpened = 22,
EditPaneExited = 23,
CommandPaneReRun = 24,
FailedToWriteConfigToDisk = 25,
}
impl EventType {
/// String value of the enum field names used in the ProtoBuf definition.
@ -459,6 +468,7 @@ impl EventType {
EventType::EditPaneOpened => "EditPaneOpened",
EventType::EditPaneExited => "EditPaneExited",
EventType::CommandPaneReRun => "CommandPaneReRun",
EventType::FailedToWriteConfigToDisk => "FailedToWriteConfigToDisk",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
@ -489,6 +499,7 @@ impl EventType {
"EditPaneOpened" => Some(Self::EditPaneOpened),
"EditPaneExited" => Some(Self::EditPaneExited),
"CommandPaneReRun" => Some(Self::CommandPaneReRun),
"FailedToWriteConfigToDisk" => Some(Self::FailedToWriteConfigToDisk),
_ => None,
}
}

View file

@ -118,8 +118,8 @@ pub mod plugin_command {
ScanHostFolderPayload(::prost::alloc::string::String),
#[prost(message, tag = "62")]
NewTabsWithLayoutInfoPayload(super::NewTabsWithLayoutInfoPayload),
#[prost(string, tag = "63")]
ReconfigurePayload(::prost::alloc::string::String),
#[prost(message, tag = "63")]
ReconfigurePayload(super::ReconfigurePayload),
#[prost(message, tag = "64")]
HidePaneWithIdPayload(super::HidePaneWithIdPayload),
#[prost(message, tag = "65")]
@ -132,6 +132,14 @@ pub mod plugin_command {
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ReconfigurePayload {
#[prost(string, tag = "1")]
pub config: ::prost::alloc::string::String,
#[prost(bool, tag = "2")]
pub write_to_disk: bool,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct RerunCommandPanePayload {
#[prost(uint32, tag = "1")]
pub terminal_pane_id: u32,

View file

@ -120,7 +120,7 @@ impl fmt::Display for KeyWithModifier {
.iter()
.map(|m| m.to_string())
.collect::<Vec<_>>()
.join("-"),
.join(" "),
self.bare_key
)
}
@ -920,6 +920,7 @@ pub enum Event {
EditPaneOpened(u32, Context), // u32 - terminal_pane_id
EditPaneExited(u32, Option<i32>, Context), // u32 - terminal_pane_id, Option<i32> - exit code
CommandPaneReRun(u32, Context), // u32 - terminal_pane_id, Option<i32> -
FailedToWriteConfigToDisk(Option<String>), // String -> the file path we failed to write
}
#[derive(
@ -1799,7 +1800,8 @@ pub enum PluginCommand {
DumpSessionLayout,
CloseSelf,
NewTabsWithLayoutInfo(LayoutInfo),
Reconfigure(String), // String -> stringified configuration
Reconfigure(String, bool), // String -> stringified configuration, bool -> save configuration
// file to disk
HidePaneWithId(PaneId),
ShowPaneWithId(PaneId, bool), // bool -> should_float_if_hidden
OpenCommandPaneBackground(CommandToRun, Context),

View file

@ -64,4 +64,7 @@ impl EnvironmentVariables {
set_var(k, v);
}
}
pub fn inner(&self) -> &HashMap<String, String> {
&self.env
}
}

View file

@ -411,6 +411,7 @@ pub enum PluginContext {
DumpLayoutToPlugin,
ListClientsMetadata,
Reconfigure,
FailedToWriteConfigToDisk,
}
/// Stack call representations corresponding to the different types of [`ClientInstruction`]s.
@ -434,6 +435,7 @@ pub enum ClientContext {
UnblockCliPipeInput,
CliPipeOutput,
QueryTerminalSize,
WriteConfigToDisk,
}
/// Stack call representations corresponding to the different types of [`ServerInstruction`]s.
@ -460,6 +462,8 @@ pub enum ServerContext {
ChangeMode,
ChangeModeForAllClients,
Reconfigure,
ConfigWrittenToDisk,
FailedToWriteConfigToDisk,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]

View file

@ -1,5 +1,6 @@
use crate::data::Palette;
use miette::{Diagnostic, LabeledSpan, NamedSource, SourceCode};
use serde::{Deserialize, Serialize};
use std::fs::File;
use std::io::{self, Read};
use std::path::PathBuf;
@ -20,7 +21,7 @@ const DEFAULT_CONFIG_FILE_NAME: &str = "config.kdl";
type ConfigResult = Result<Config, ConfigError>;
/// Main configuration.
#[derive(Debug, Clone, PartialEq, Default)]
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct Config {
pub keybinds: Keybinds,
pub options: Options,
@ -233,6 +234,164 @@ impl Config {
self.env = self.env.merge(other.env);
Ok(())
}
fn config_file_path(opts: &CliArgs) -> Option<PathBuf> {
opts.config_dir
.clone()
.or_else(home::find_default_config_dir)
.map(|config_dir| config_dir.join(DEFAULT_CONFIG_FILE_NAME))
}
pub fn write_config_to_disk(config: String, opts: &CliArgs) -> Result<Config, Option<PathBuf>> {
// if we fail, try to return the PathBuf of the file we were not able to write to
Config::from_kdl(&config, None)
.map_err(|e| {
log::error!("Failed to parse config: {}", e);
None
})
.and_then(|parsed_config| {
let backed_up_file_name = Config::backup_current_config(&opts)?;
let config_file_path = Config::config_file_path(&opts).ok_or_else(|| {
log::error!("Config file path not found");
None
})?;
let config = match backed_up_file_name {
Some(backed_up_file_name) => {
format!(
"{}{}",
Config::autogen_config_message(backed_up_file_name),
config
)
},
None => config,
};
std::fs::write(&config_file_path, config.as_bytes()).map_err(|e| {
log::error!("Failed to write config: {}", e);
Some(config_file_path.clone())
})?;
let written_config = std::fs::read_to_string(&config_file_path).map_err(|e| {
log::error!("Failed to read written config: {}", e);
Some(config_file_path.clone())
})?;
let parsed_written_config =
Config::from_kdl(&written_config, None).map_err(|e| {
log::error!("Failed to parse written config: {}", e);
None
})?;
if parsed_written_config == parsed_config {
Ok(parsed_config)
} else {
log::error!("Configuration corrupted when writing to disk");
Err(Some(config_file_path))
}
})
}
// returns true if the config was not previouly written to disk and we successfully wrote it
pub fn write_config_to_disk_if_it_does_not_exist(config: String, opts: &CliArgs) -> bool {
match Config::config_file_path(opts) {
Some(config_file_path) => {
if config_file_path.exists() {
false
} else {
if let Err(e) = std::fs::write(&config_file_path, config.as_bytes()) {
log::error!("Failed to write config to disk: {}", e);
return false;
}
match std::fs::read_to_string(&config_file_path) {
Ok(written_config) => written_config == config,
Err(e) => {
log::error!("Failed to read written config: {}", e);
false
},
}
}
},
None => false,
}
}
fn find_free_backup_file_name(config_file_path: &PathBuf) -> Option<PathBuf> {
let mut backup_config_path = None;
let config_file_name = config_file_path
.file_name()
.and_then(|f| f.to_str())
.unwrap_or_else(|| DEFAULT_CONFIG_FILE_NAME);
for i in 0..100 {
let new_file_name = if i == 0 {
format!("{}.bak", config_file_name)
} else {
format!("{}.bak.{}", config_file_name, i)
};
let mut potential_config_path = config_file_path.clone();
potential_config_path.set_file_name(new_file_name);
if !potential_config_path.exists() {
backup_config_path = Some(potential_config_path);
break;
}
}
backup_config_path
}
fn backup_config_with_written_content_confirmation(
current_config: &str,
current_config_file_path: &PathBuf,
backup_config_path: &PathBuf,
) -> bool {
let _ = std::fs::copy(current_config_file_path, &backup_config_path);
match std::fs::read_to_string(&backup_config_path) {
Ok(backed_up_config) => current_config == &backed_up_config,
Err(e) => {
log::error!(
"Failed to back up config file {}: {:?}",
backup_config_path.display(),
e
);
false
},
}
}
fn backup_current_config(opts: &CliArgs) -> Result<Option<PathBuf>, Option<PathBuf>> {
// if we fail, try to return the PathBuf of the file we were not able to write to
if let Some(config_file_path) = Config::config_file_path(&opts) {
match std::fs::read_to_string(&config_file_path) {
Ok(current_config) => {
let Some(backup_config_path) =
Config::find_free_backup_file_name(&config_file_path)
else {
log::error!("Failed to find a file name to back up the configuration to, ran out of files.");
return Err(None);
};
if Config::backup_config_with_written_content_confirmation(
&current_config,
&config_file_path,
&backup_config_path,
) {
Ok(Some(backup_config_path))
} else {
log::error!(
"Failed to back up config file: {}",
backup_config_path.display()
);
Err(Some(backup_config_path))
}
},
Err(e) => {
if e.kind() == std::io::ErrorKind::NotFound {
Ok(None)
} else {
log::error!(
"Failed to read current config {}: {}",
config_file_path.display(),
e
);
Err(Some(config_file_path))
}
},
}
} else {
log::error!("No config file path found?");
Err(None)
}
}
fn autogen_config_message(backed_up_file_name: PathBuf) -> String {
format!("//\n// THIS FILE WAS AUTOGENERATED BY ZELLIJ, THE PREVIOUS FILE AT THIS LOCATION WAS COPIED TO: {}\n//\n\n", backed_up_file_name.display())
}
}
#[cfg(test)]
@ -463,6 +622,7 @@ mod config_test {
white: PaletteColor::Rgb((255, 255, 255)),
..Default::default()
},
sourced_from_external_file: false,
},
);
let expected_themes = Themes::from_data(expected_themes);
@ -520,6 +680,7 @@ mod config_test {
white: PaletteColor::Rgb((255, 255, 255)),
..Default::default()
},
sourced_from_external_file: false,
},
);
expected_themes.insert(
@ -539,6 +700,7 @@ mod config_test {
orange: PaletteColor::Rgb((208, 135, 112)),
..Default::default()
},
sourced_from_external_file: false,
},
);
let expected_themes = Themes::from_data(expected_themes);
@ -583,6 +745,7 @@ mod config_test {
white: PaletteColor::EightBit(255),
..Default::default()
},
sourced_from_external_file: false,
},
);
let expected_themes = Themes::from_data(expected_themes);

View file

@ -504,6 +504,12 @@ impl PluginUserConfiguration {
configuration.remove("direction");
configuration.remove("floating");
configuration.remove("move_to_focused_tab");
configuration.remove("launch_new");
configuration.remove("payload");
configuration.remove("skip_cache");
configuration.remove("title");
configuration.remove("in_place");
configuration.remove("skip_plugin_cache");
PluginUserConfiguration(configuration)
}
@ -735,6 +741,19 @@ pub struct FloatingPaneLayout {
}
impl FloatingPaneLayout {
pub fn new() -> Self {
FloatingPaneLayout {
name: None,
height: None,
width: None,
x: None,
y: None,
run: None,
focus: None,
already_running: false,
pane_initial_contents: None,
}
}
pub fn add_cwd_to_layout(&mut self, cwd: &PathBuf) {
match self.run.as_mut() {
Some(run) => run.add_cwd(cwd),

View file

@ -37,7 +37,7 @@ impl FrameConfig {
}
}
#[derive(Clone, PartialEq, Default)]
#[derive(Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct Themes(HashMap<String, Theme>);
impl fmt::Debug for Themes {
@ -67,12 +67,16 @@ impl Themes {
pub fn get_theme(&self, theme_name: &str) -> Option<&Theme> {
self.0.get(theme_name)
}
pub fn inner(&self) -> &HashMap<String, Theme> {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct Theme {
#[serde(flatten)]
pub palette: Palette,
pub sourced_from_external_file: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]

View file

@ -1,5 +1,6 @@
---
source: zellij-utils/src/input/./unit/theme_test.rs
assertion_line: 15
expression: "format!(\"{:#?}\", theme)"
---
{
@ -103,5 +104,6 @@ expression: "format!(\"{:#?}\", theme)"
0,
),
},
sourced_from_external_file: true,
},
}

View file

@ -3,6 +3,7 @@ use crate::{
cli::CliArgs,
data::{ClientId, ConnectToSession, KeyWithModifier, Style},
errors::{get_current_ctx, prelude::*, ErrorContext},
input::config::Config,
input::keybinds::Keybinds,
input::{actions::Action, layout::Layout, options::Options, plugins::PluginAliases},
pane_size::{Size, SizeInPixels},
@ -16,6 +17,7 @@ use std::{
io::{self, Write},
marker::PhantomData,
os::unix::io::{AsRawFd, FromRawFd},
path::PathBuf,
};
type SessionId = u64;
@ -41,7 +43,6 @@ pub enum ClientType {
pub struct ClientAttributes {
pub size: Size,
pub style: Style,
pub keybinds: Keybinds,
}
#[derive(Default, Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
@ -74,13 +75,16 @@ pub enum ClientToServerMsg {
NewClient(
ClientAttributes,
Box<CliArgs>,
Box<Options>,
Box<Config>, // represents the saved configuration
Box<Options>, // represents the runtime configuration
Box<Layout>,
Box<PluginAliases>,
bool, // should launch setup wizard
),
AttachClient(
ClientAttributes,
Options,
Config, // represents the saved configuration
Options, // represents the runtime configuration
Option<usize>, // tab position to focus
Option<(u32, bool)>, // (pane_id, is_plugin) => pane id to focus
),
@ -90,6 +94,8 @@ pub enum ClientToServerMsg {
KillSession,
ConnStatus,
ListClients,
ConfigWrittenToDisk(Config),
FailedToWriteConfigToDisk(Option<PathBuf>),
}
// Types of messages sent from the server to the client
@ -106,6 +112,7 @@ pub enum ServerToClientMsg {
UnblockCliPipeInput(String), // String -> pipe name
CliPipeOutput(String, String), // String -> pipe name, String -> Output
QueryTerminalSize,
WriteConfigToDisk { config: String },
}
#[derive(Serialize, Deserialize, Debug, Clone)]

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,240 @@
---
source: zellij-utils/src/kdl/mod.rs
assertion_line: 5060
expression: fake_config_stringified
---
keybinds clear-defaults=true {
locked {
bind "Ctrl g" { SwitchToMode "normal"; }
}
pane {
bind "left" { MoveFocus "left"; }
bind "down" { MoveFocus "down"; }
bind "up" { MoveFocus "up"; }
bind "right" { MoveFocus "right"; }
bind "c" { SwitchToMode "renamepane"; PaneNameInput 0; }
bind "d" { NewPane "down"; SwitchToMode "normal"; }
bind "e" { TogglePaneEmbedOrFloating; SwitchToMode "normal"; }
bind "f" { ToggleFocusFullscreen; SwitchToMode "normal"; }
bind "h" { MoveFocus "left"; }
bind "j" { MoveFocus "down"; }
bind "k" { MoveFocus "up"; }
bind "l" { MoveFocus "right"; }
bind "n" { NewPane; SwitchToMode "normal"; }
bind "p" { SwitchFocus; }
bind "Ctrl p" { SwitchToMode "normal"; }
bind "r" { NewPane "right"; SwitchToMode "normal"; }
bind "w" { ToggleFloatingPanes; SwitchToMode "normal"; }
bind "z" { TogglePaneFrames; SwitchToMode "normal"; }
}
tab {
bind "left" { GoToPreviousTab; }
bind "down" { GoToNextTab; }
bind "up" { GoToPreviousTab; }
bind "right" { GoToNextTab; }
bind "1" { GoToTab 1; SwitchToMode "normal"; }
bind "2" { GoToTab 2; SwitchToMode "normal"; }
bind "3" { GoToTab 3; SwitchToMode "normal"; }
bind "4" { GoToTab 4; SwitchToMode "normal"; }
bind "5" { GoToTab 5; SwitchToMode "normal"; }
bind "6" { GoToTab 6; SwitchToMode "normal"; }
bind "7" { GoToTab 7; SwitchToMode "normal"; }
bind "8" { GoToTab 8; SwitchToMode "normal"; }
bind "9" { GoToTab 9; SwitchToMode "normal"; }
bind "[" { BreakPaneLeft; SwitchToMode "normal"; }
bind "]" { BreakPaneRight; SwitchToMode "normal"; }
bind "b" { BreakPane; SwitchToMode "normal"; }
bind "h" { GoToPreviousTab; }
bind "j" { GoToNextTab; }
bind "k" { GoToPreviousTab; }
bind "l" { GoToNextTab; }
bind "n" { NewTab; SwitchToMode "normal"; }
bind "r" { SwitchToMode "renametab"; TabNameInput 0; }
bind "s" { ToggleActiveSyncTab; SwitchToMode "normal"; }
bind "Ctrl t" { SwitchToMode "normal"; }
bind "x" { CloseTab; SwitchToMode "normal"; }
bind "tab" { ToggleTab; }
}
resize {
bind "left" { Resize "Increase left"; }
bind "down" { Resize "Increase down"; }
bind "up" { Resize "Increase up"; }
bind "right" { Resize "Increase right"; }
bind "+" { Resize "Increase"; }
bind "-" { Resize "Decrease"; }
bind "=" { Resize "Increase"; }
bind "H" { Resize "Decrease left"; }
bind "J" { Resize "Decrease down"; }
bind "K" { Resize "Decrease up"; }
bind "L" { Resize "Decrease right"; }
bind "h" { Resize "Increase left"; }
bind "j" { Resize "Increase down"; }
bind "k" { Resize "Increase up"; }
bind "l" { Resize "Increase right"; }
bind "Ctrl n" { SwitchToMode "normal"; }
}
move {
bind "left" { MovePane "left"; }
bind "down" { MovePane "down"; }
bind "up" { MovePane "up"; }
bind "right" { MovePane "right"; }
bind "h" { MovePane "left"; }
bind "Ctrl h" { SwitchToMode "normal"; }
bind "j" { MovePane "down"; }
bind "k" { MovePane "up"; }
bind "l" { MovePane "right"; }
bind "n" { MovePane; }
bind "p" { MovePaneBackwards; }
bind "tab" { MovePane; }
}
scroll {
bind "e" { EditScrollback; SwitchToMode "normal"; }
bind "s" { SwitchToMode "entersearch"; SearchInput 0; }
}
search {
bind "c" { SearchToggleOption "CaseSensitivity"; }
bind "n" { Search "down"; }
bind "o" { SearchToggleOption "WholeWord"; }
bind "p" { Search "up"; }
bind "w" { SearchToggleOption "Wrap"; }
}
session {
bind "c" {
LaunchOrFocusPlugin "configuration" {
floating true
move_to_focused_tab true
}
SwitchToMode "normal"
}
bind "Ctrl o" { SwitchToMode "normal"; }
bind "w" {
LaunchOrFocusPlugin "session-manager" {
floating true
move_to_focused_tab true
}
SwitchToMode "normal"
}
}
shared_except "locked" {
bind "Alt left" { MoveFocusOrTab "left"; }
bind "Alt down" { MoveFocus "down"; }
bind "Alt up" { MoveFocus "up"; }
bind "Alt right" { MoveFocusOrTab "right"; }
bind "Alt +" { Resize "Increase"; }
bind "Alt -" { Resize "Decrease"; }
bind "Alt =" { Resize "Increase"; }
bind "Alt [" { PreviousSwapLayout; }
bind "Alt ]" { NextSwapLayout; }
bind "Alt f" { ToggleFloatingPanes; }
bind "Ctrl g" { SwitchToMode "locked"; }
bind "Alt h" { MoveFocusOrTab "left"; }
bind "Alt i" { MoveTab "left"; }
bind "Alt j" { MoveFocus "down"; }
bind "Alt k" { MoveFocus "up"; }
bind "Alt l" { MoveFocusOrTab "right"; }
bind "Alt n" { NewPane; }
bind "Alt o" { MoveTab "right"; }
bind "Ctrl q" { Quit; }
}
shared_except "locked" "move" {
bind "Ctrl h" { SwitchToMode "move"; }
}
shared_except "locked" "session" {
bind "Ctrl o" { SwitchToMode "session"; }
}
shared_except "locked" "scroll" "search" "tmux" {
bind "Ctrl b" { SwitchToMode "tmux"; }
}
shared_except "locked" "scroll" "search" {
bind "Ctrl s" { SwitchToMode "scroll"; }
}
shared_except "locked" "tab" {
bind "Ctrl t" { SwitchToMode "tab"; }
}
shared_except "locked" "pane" {
bind "Ctrl p" { SwitchToMode "pane"; }
}
shared_except "locked" "resize" {
bind "Ctrl n" { SwitchToMode "resize"; }
}
shared_except "normal" "locked" "entersearch" {
bind "enter" { SwitchToMode "normal"; }
}
shared_except "normal" "locked" "entersearch" "renametab" "renamepane" {
bind "esc" { SwitchToMode "normal"; }
}
shared_among "pane" "tmux" {
bind "x" { CloseFocus; SwitchToMode "normal"; }
}
shared_among "scroll" "search" {
bind "PageDown" { PageScrollDown; }
bind "PageUp" { PageScrollUp; }
bind "left" { PageScrollUp; }
bind "down" { ScrollDown; }
bind "up" { ScrollUp; }
bind "right" { PageScrollDown; }
bind "Ctrl b" { PageScrollUp; }
bind "Ctrl c" { ScrollToBottom; SwitchToMode "normal"; }
bind "d" { HalfPageScrollDown; }
bind "Ctrl f" { PageScrollDown; }
bind "h" { PageScrollUp; }
bind "j" { ScrollDown; }
bind "k" { ScrollUp; }
bind "l" { PageScrollDown; }
bind "Ctrl s" { SwitchToMode "normal"; }
bind "u" { HalfPageScrollUp; }
}
entersearch {
bind "Ctrl c" { SwitchToMode "scroll"; }
bind "esc" { SwitchToMode "scroll"; }
bind "enter" { SwitchToMode "search"; }
}
renametab {
bind "esc" { UndoRenameTab; SwitchToMode "tab"; }
}
shared_among "renametab" "renamepane" {
bind "Ctrl c" { SwitchToMode "normal"; }
}
renamepane {
bind "esc" { UndoRenamePane; SwitchToMode "pane"; }
}
shared_among "session" "tmux" {
bind "d" { Detach; }
}
tmux {
bind "left" { MoveFocus "left"; SwitchToMode "normal"; }
bind "down" { MoveFocus "down"; SwitchToMode "normal"; }
bind "up" { MoveFocus "up"; SwitchToMode "normal"; }
bind "right" { MoveFocus "right"; SwitchToMode "normal"; }
bind "space" { NextSwapLayout; }
bind "\"" { NewPane "down"; SwitchToMode "normal"; }
bind "%" { NewPane "right"; SwitchToMode "normal"; }
bind "," { SwitchToMode "renametab"; }
bind "[" { SwitchToMode "scroll"; }
bind "Ctrl b" { Write 2; SwitchToMode "normal"; }
bind "c" { NewTab; SwitchToMode "normal"; }
bind "h" { MoveFocus "left"; SwitchToMode "normal"; }
bind "j" { MoveFocus "down"; SwitchToMode "normal"; }
bind "k" { MoveFocus "up"; SwitchToMode "normal"; }
bind "l" { MoveFocus "right"; SwitchToMode "normal"; }
bind "n" { GoToNextTab; SwitchToMode "normal"; }
bind "o" { FocusNextPane; }
bind "p" { GoToPreviousTab; SwitchToMode "normal"; }
bind "z" { ToggleFocusFullscreen; SwitchToMode "normal"; }
}
}
plugins {
compact-bar location="zellij:compact-bar"
configuration location="zellij:configuration"
filepicker location="zellij:strider" {
cwd "/"
}
session-manager location="zellij:session-manager"
status-bar location="zellij:status-bar"
strider location="zellij:strider"
tab-bar location="zellij:tab-bar"
welcome-screen location="zellij:session-manager" {
welcome_screen true
}
}

View file

@ -0,0 +1,409 @@
---
source: zellij-utils/src/kdl/mod.rs
assertion_line: 5069
expression: fake_config_stringified
---
keybinds clear-defaults=true {
locked {
bind "Ctrl g" { SwitchToMode "normal"; }
}
pane {
bind "left" { MoveFocus "left"; }
bind "down" { MoveFocus "down"; }
bind "up" { MoveFocus "up"; }
bind "right" { MoveFocus "right"; }
bind "c" { SwitchToMode "renamepane"; PaneNameInput 0; }
bind "d" { NewPane "down"; SwitchToMode "normal"; }
bind "e" { TogglePaneEmbedOrFloating; SwitchToMode "normal"; }
bind "f" { ToggleFocusFullscreen; SwitchToMode "normal"; }
bind "h" { MoveFocus "left"; }
bind "j" { MoveFocus "down"; }
bind "k" { MoveFocus "up"; }
bind "l" { MoveFocus "right"; }
bind "n" { NewPane; SwitchToMode "normal"; }
bind "p" { SwitchFocus; }
bind "Ctrl p" { SwitchToMode "normal"; }
bind "r" { NewPane "right"; SwitchToMode "normal"; }
bind "w" { ToggleFloatingPanes; SwitchToMode "normal"; }
bind "z" { TogglePaneFrames; SwitchToMode "normal"; }
}
tab {
bind "left" { GoToPreviousTab; }
bind "down" { GoToNextTab; }
bind "up" { GoToPreviousTab; }
bind "right" { GoToNextTab; }
bind "1" { GoToTab 1; SwitchToMode "normal"; }
bind "2" { GoToTab 2; SwitchToMode "normal"; }
bind "3" { GoToTab 3; SwitchToMode "normal"; }
bind "4" { GoToTab 4; SwitchToMode "normal"; }
bind "5" { GoToTab 5; SwitchToMode "normal"; }
bind "6" { GoToTab 6; SwitchToMode "normal"; }
bind "7" { GoToTab 7; SwitchToMode "normal"; }
bind "8" { GoToTab 8; SwitchToMode "normal"; }
bind "9" { GoToTab 9; SwitchToMode "normal"; }
bind "[" { BreakPaneLeft; SwitchToMode "normal"; }
bind "]" { BreakPaneRight; SwitchToMode "normal"; }
bind "b" { BreakPane; SwitchToMode "normal"; }
bind "h" { GoToPreviousTab; }
bind "j" { GoToNextTab; }
bind "k" { GoToPreviousTab; }
bind "l" { GoToNextTab; }
bind "n" { NewTab; SwitchToMode "normal"; }
bind "r" { SwitchToMode "renametab"; TabNameInput 0; }
bind "s" { ToggleActiveSyncTab; SwitchToMode "normal"; }
bind "Ctrl t" { SwitchToMode "normal"; }
bind "x" { CloseTab; SwitchToMode "normal"; }
bind "tab" { ToggleTab; }
}
resize {
bind "left" { Resize "Increase left"; }
bind "down" { Resize "Increase down"; }
bind "up" { Resize "Increase up"; }
bind "right" { Resize "Increase right"; }
bind "+" { Resize "Increase"; }
bind "-" { Resize "Decrease"; }
bind "=" { Resize "Increase"; }
bind "H" { Resize "Decrease left"; }
bind "J" { Resize "Decrease down"; }
bind "K" { Resize "Decrease up"; }
bind "L" { Resize "Decrease right"; }
bind "h" { Resize "Increase left"; }
bind "j" { Resize "Increase down"; }
bind "k" { Resize "Increase up"; }
bind "l" { Resize "Increase right"; }
bind "Ctrl n" { SwitchToMode "normal"; }
}
move {
bind "left" { MovePane "left"; }
bind "down" { MovePane "down"; }
bind "up" { MovePane "up"; }
bind "right" { MovePane "right"; }
bind "h" { MovePane "left"; }
bind "Ctrl h" { SwitchToMode "normal"; }
bind "j" { MovePane "down"; }
bind "k" { MovePane "up"; }
bind "l" { MovePane "right"; }
bind "n" { MovePane; }
bind "p" { MovePaneBackwards; }
bind "tab" { MovePane; }
}
scroll {
bind "e" { EditScrollback; SwitchToMode "normal"; }
bind "s" { SwitchToMode "entersearch"; SearchInput 0; }
}
search {
bind "c" { SearchToggleOption "CaseSensitivity"; }
bind "n" { Search "down"; }
bind "o" { SearchToggleOption "WholeWord"; }
bind "p" { Search "up"; }
bind "w" { SearchToggleOption "Wrap"; }
}
session {
bind "c" {
LaunchOrFocusPlugin "configuration" {
floating true
move_to_focused_tab true
}
SwitchToMode "normal"
}
bind "Ctrl o" { SwitchToMode "normal"; }
bind "w" {
LaunchOrFocusPlugin "session-manager" {
floating true
move_to_focused_tab true
}
SwitchToMode "normal"
}
}
shared_except "locked" {
bind "Alt left" { MoveFocusOrTab "left"; }
bind "Alt down" { MoveFocus "down"; }
bind "Alt up" { MoveFocus "up"; }
bind "Alt right" { MoveFocusOrTab "right"; }
bind "Alt +" { Resize "Increase"; }
bind "Alt -" { Resize "Decrease"; }
bind "Alt =" { Resize "Increase"; }
bind "Alt [" { PreviousSwapLayout; }
bind "Alt ]" { NextSwapLayout; }
bind "Alt f" { ToggleFloatingPanes; }
bind "Ctrl g" { SwitchToMode "locked"; }
bind "Alt h" { MoveFocusOrTab "left"; }
bind "Alt i" { MoveTab "left"; }
bind "Alt j" { MoveFocus "down"; }
bind "Alt k" { MoveFocus "up"; }
bind "Alt l" { MoveFocusOrTab "right"; }
bind "Alt n" { NewPane; }
bind "Alt o" { MoveTab "right"; }
bind "Ctrl q" { Quit; }
}
shared_except "locked" "move" {
bind "Ctrl h" { SwitchToMode "move"; }
}
shared_except "locked" "session" {
bind "Ctrl o" { SwitchToMode "session"; }
}
shared_except "locked" "scroll" "search" "tmux" {
bind "Ctrl b" { SwitchToMode "tmux"; }
}
shared_except "locked" "scroll" "search" {
bind "Ctrl s" { SwitchToMode "scroll"; }
}
shared_except "locked" "tab" {
bind "Ctrl t" { SwitchToMode "tab"; }
}
shared_except "locked" "pane" {
bind "Ctrl p" { SwitchToMode "pane"; }
}
shared_except "locked" "resize" {
bind "Ctrl n" { SwitchToMode "resize"; }
}
shared_except "normal" "locked" "entersearch" {
bind "enter" { SwitchToMode "normal"; }
}
shared_except "normal" "locked" "entersearch" "renametab" "renamepane" {
bind "esc" { SwitchToMode "normal"; }
}
shared_among "pane" "tmux" {
bind "x" { CloseFocus; SwitchToMode "normal"; }
}
shared_among "scroll" "search" {
bind "PageDown" { PageScrollDown; }
bind "PageUp" { PageScrollUp; }
bind "left" { PageScrollUp; }
bind "down" { ScrollDown; }
bind "up" { ScrollUp; }
bind "right" { PageScrollDown; }
bind "Ctrl b" { PageScrollUp; }
bind "Ctrl c" { ScrollToBottom; SwitchToMode "normal"; }
bind "d" { HalfPageScrollDown; }
bind "Ctrl f" { PageScrollDown; }
bind "h" { PageScrollUp; }
bind "j" { ScrollDown; }
bind "k" { ScrollUp; }
bind "l" { PageScrollDown; }
bind "Ctrl s" { SwitchToMode "normal"; }
bind "u" { HalfPageScrollUp; }
}
entersearch {
bind "Ctrl c" { SwitchToMode "scroll"; }
bind "esc" { SwitchToMode "scroll"; }
bind "enter" { SwitchToMode "search"; }
}
renametab {
bind "esc" { UndoRenameTab; SwitchToMode "tab"; }
}
shared_among "renametab" "renamepane" {
bind "Ctrl c" { SwitchToMode "normal"; }
}
renamepane {
bind "esc" { UndoRenamePane; SwitchToMode "pane"; }
}
shared_among "session" "tmux" {
bind "d" { Detach; }
}
tmux {
bind "left" { MoveFocus "left"; SwitchToMode "normal"; }
bind "down" { MoveFocus "down"; SwitchToMode "normal"; }
bind "up" { MoveFocus "up"; SwitchToMode "normal"; }
bind "right" { MoveFocus "right"; SwitchToMode "normal"; }
bind "space" { NextSwapLayout; }
bind "\"" { NewPane "down"; SwitchToMode "normal"; }
bind "%" { NewPane "right"; SwitchToMode "normal"; }
bind "," { SwitchToMode "renametab"; }
bind "[" { SwitchToMode "scroll"; }
bind "Ctrl b" { Write 2; SwitchToMode "normal"; }
bind "c" { NewTab; SwitchToMode "normal"; }
bind "h" { MoveFocus "left"; SwitchToMode "normal"; }
bind "j" { MoveFocus "down"; SwitchToMode "normal"; }
bind "k" { MoveFocus "up"; SwitchToMode "normal"; }
bind "l" { MoveFocus "right"; SwitchToMode "normal"; }
bind "n" { GoToNextTab; SwitchToMode "normal"; }
bind "o" { FocusNextPane; }
bind "p" { GoToPreviousTab; SwitchToMode "normal"; }
bind "z" { ToggleFocusFullscreen; SwitchToMode "normal"; }
}
}
plugins {
compact-bar location="zellij:compact-bar"
configuration location="zellij:configuration"
filepicker location="zellij:strider" {
cwd "/"
}
session-manager location="zellij:session-manager"
status-bar location="zellij:status-bar"
strider location="zellij:strider"
tab-bar location="zellij:tab-bar"
welcome-screen location="zellij:session-manager" {
welcome_screen true
}
}
// Use a simplified UI without special fonts (arrow glyphs)
// Options:
// - true
// - false (Default)
//
// simplified_ui true
// Choose the theme that is specified in the themes section.
// Default: default
//
// theme "dracula"
// Choose the base input mode of zellij.
// Default: normal
//
// default_mode "locked"
// Choose the path to the default shell that zellij will use for opening new panes
// Default: $SHELL
//
// default_shell "fish"
// Choose the path to override cwd that zellij will use for opening new panes
//
// default_cwd "/tmp"
// The name of the default layout to load on startup
// Default: "default"
//
// default_layout "compact"
// The folder in which Zellij will look for layouts
//
// layout_dir "/tmp"
// The folder in which Zellij will look for themes
//
// theme_dir "/tmp"
// Toggle enabling the mouse mode.
// On certain configurations, or terminals this could
// potentially interfere with copying text.
// Options:
// - true (default)
// - false
//
// mouse_mode false
// Toggle having pane frames around the panes
// Options:
// - true (default, enabled)
// - false
//
// pane_frames false
// When attaching to an existing session with other users,
// should the session be mirrored (true)
// or should each user have their own cursor (false)
// Default: false
//
// mirror_session true
// Choose what to do when zellij receives SIGTERM, SIGINT, SIGQUIT or SIGHUP
// eg. when terminal window with an active zellij session is closed
// Options:
// - detach (Default)
// - quit
//
// on_force_close "quit"
// Configure the scroll back buffer size
// This is the number of lines zellij stores for each pane in the scroll back
// buffer. Excess number of lines are discarded in a FIFO fashion.
// Valid values: positive integers
// Default value: 10000
//
// scroll_buffer_size 10000
// Provide a command to execute when copying text. The text will be piped to
// the stdin of the program to perform the copy. This can be used with
// terminal emulators which do not support the OSC 52 ANSI control sequence
// that will be used by default if this option is not set.
// Examples:
//
// copy_command "xclip -selection clipboard" // x11
// copy_command "wl-copy" // wayland
// copy_command "pbcopy" // osx
//
// copy_command "pbcopy"
// Choose the destination for copied text
// Allows using the primary selection buffer (on x11/wayland) instead of the system clipboard.
// Does not apply when using copy_command.
// Options:
// - system (default)
// - primary
//
// copy_clipboard "primary"
// Enable automatic copying (and clearing) of selection when releasing mouse
// Default: true
//
// copy_on_select true
// Path to the default editor to use to edit pane scrollbuffer
// Default: $EDITOR or $VISUAL
// scrollback_editor "/usr/bin/vim"
// A fixed name to always give the Zellij session.
// Consider also setting `attach_to_session true,`
// otherwise this will error if such a session exists.
// Default: <RANDOM>
//
// session_name "My singleton session"
// When `session_name` is provided, attaches to that session
// if it is already running or creates it otherwise.
// Default: false
//
// attach_to_session true
// Toggle between having Zellij lay out panes according to a predefined set of layouts whenever possible
// Options:
// - true (default)
// - false
//
// auto_layout false
// Whether sessions should be serialized to the cache folder (including their tabs/panes, cwds and running commands) so that they can later be resurrected
// Options:
// - true (default)
// - false
//
// session_serialization false
// Whether pane viewports are serialized along with the session, default is false
// Options:
// - true
// - false (default)
//
// serialize_pane_viewport false
// Scrollback lines to serialize along with the pane viewport when serializing sessions, 0
// defaults to the scrollback size. If this number is higher than the scrollback size, it will
// also default to the scrollback size. This does nothing if `serialize_pane_viewport` is not true.
//
// scrollback_lines_to_serialize 10000
// Enable or disable the rendering of styled and colored underlines (undercurl).
// May need to be disabled for certain unsupported terminals
// Default: true
//
// styled_underlines false
// How often in seconds sessions are serialized
//
// serialization_interval 10000
// Enable or disable writing of session metadata to disk (if disabled, other sessions might not know
// metadata info on this session)
// Default: false
//
// disable_session_metadata false
// Enable or disable support for the enhanced Kitty Keyboard Protocol (the host terminal must also support it)
// Default: true (if the host terminal supports it)
//
// support_kitty_keyboard_protocol false

View file

@ -0,0 +1,33 @@
---
source: zellij-utils/src/kdl/mod.rs
assertion_line: 4182
expression: fake_document.to_string()
---
simplified_ui true
theme "dracula"
default_mode "locked"
default_shell "fish"
default_cwd "/tmp/foo"
default_layout "compact"
layout_dir "/tmp/layouts"
theme_dir "/tmp/themes"
mouse_mode false
pane_frames false
mirror_session true
on_force_close "quit"
scroll_buffer_size 100
copy_command "pbcopy"
copy_clipboard "system"
copy_on_select false
scrollback_editor "vim"
session_name "my_cool_session"
attach_to_session false
auto_layout false
session_serialization true
serialize_pane_viewport false
scrollback_lines_to_serialize 1000
styled_underlines false
serialization_interval 1
disable_session_metadata true
support_kitty_keyboard_protocol false

View file

@ -0,0 +1,175 @@
---
source: zellij-utils/src/kdl/mod.rs
assertion_line: 4987
expression: fake_document.to_string()
---
// Use a simplified UI without special fonts (arrow glyphs)
// Options:
// - true
// - false (Default)
//
simplified_ui true
// Choose the theme that is specified in the themes section.
// Default: default
//
theme "dracula"
// Choose the base input mode of zellij.
// Default: normal
//
default_mode "locked"
// Choose the path to the default shell that zellij will use for opening new panes
// Default: $SHELL
//
default_shell "fish"
// Choose the path to override cwd that zellij will use for opening new panes
//
default_cwd "/tmp/foo"
// The name of the default layout to load on startup
// Default: "default"
//
default_layout "compact"
// The folder in which Zellij will look for layouts
//
layout_dir "/tmp/layouts"
// The folder in which Zellij will look for themes
//
theme_dir "/tmp/themes"
// Toggle enabling the mouse mode.
// On certain configurations, or terminals this could
// potentially interfere with copying text.
// Options:
// - true (default)
// - false
//
mouse_mode false
// Toggle having pane frames around the panes
// Options:
// - true (default, enabled)
// - false
//
pane_frames false
// When attaching to an existing session with other users,
// should the session be mirrored (true)
// or should each user have their own cursor (false)
// Default: false
//
mirror_session true
// Choose what to do when zellij receives SIGTERM, SIGINT, SIGQUIT or SIGHUP
// eg. when terminal window with an active zellij session is closed
// Options:
// - detach (Default)
// - quit
//
on_force_close "quit"
// Configure the scroll back buffer size
// This is the number of lines zellij stores for each pane in the scroll back
// buffer. Excess number of lines are discarded in a FIFO fashion.
// Valid values: positive integers
// Default value: 10000
//
scroll_buffer_size 100
// Provide a command to execute when copying text. The text will be piped to
// the stdin of the program to perform the copy. This can be used with
// terminal emulators which do not support the OSC 52 ANSI control sequence
// that will be used by default if this option is not set.
// Examples:
//
// copy_command "xclip -selection clipboard" // x11
// copy_command "wl-copy" // wayland
// copy_command "pbcopy" // osx
//
copy_command "pbcopy"
// Choose the destination for copied text
// Allows using the primary selection buffer (on x11/wayland) instead of the system clipboard.
// Does not apply when using copy_command.
// Options:
// - system (default)
// - primary
//
copy_clipboard "system"
// Enable automatic copying (and clearing) of selection when releasing mouse
// Default: true
//
copy_on_select false
// Path to the default editor to use to edit pane scrollbuffer
// Default: $EDITOR or $VISUAL
scrollback_editor "vim"
// A fixed name to always give the Zellij session.
// Consider also setting `attach_to_session true,`
// otherwise this will error if such a session exists.
// Default: <RANDOM>
//
session_name "my_cool_session"
// When `session_name` is provided, attaches to that session
// if it is already running or creates it otherwise.
// Default: false
//
attach_to_session false
// Toggle between having Zellij lay out panes according to a predefined set of layouts whenever possible
// Options:
// - true (default)
// - false
//
auto_layout false
// Whether sessions should be serialized to the cache folder (including their tabs/panes, cwds and running commands) so that they can later be resurrected
// Options:
// - true (default)
// - false
//
session_serialization true
// Whether pane viewports are serialized along with the session, default is false
// Options:
// - true
// - false (default)
//
serialize_pane_viewport false
// Scrollback lines to serialize along with the pane viewport when serializing sessions, 0
// defaults to the scrollback size. If this number is higher than the scrollback size, it will
// also default to the scrollback size. This does nothing if `serialize_pane_viewport` is not true.
//
scrollback_lines_to_serialize 1000
// Enable or disable the rendering of styled and colored underlines (undercurl).
// May need to be disabled for certain unsupported terminals
// Default: true
//
styled_underlines false
// How often in seconds sessions are serialized
//
serialization_interval 1
// Enable or disable writing of session metadata to disk (if disabled, other sessions might not know
// metadata info on this session)
// Default: false
//
disable_session_metadata true
// Enable or disable support for the enhanced Kitty Keyboard Protocol (the host terminal must also support it)
// Default: true (if the host terminal supports it)
//
support_kitty_keyboard_protocol false

View file

@ -0,0 +1,7 @@
---
source: zellij-utils/src/kdl/mod.rs
assertion_line: 4211
expression: fake_document.to_string()
---
default_layout "compact"

View file

@ -0,0 +1,6 @@
---
source: zellij-utils/src/kdl/mod.rs
assertion_line: 4196
expression: fake_document.to_string()
---

View file

@ -0,0 +1,11 @@
---
source: zellij-utils/src/kdl/mod.rs
assertion_line: 3981
expression: serialized.to_string()
---
env {
bar "foo"
baz "true"
foo "bar"
thing "1"
}

View file

@ -0,0 +1,11 @@
---
source: zellij-utils/src/kdl/mod.rs
assertion_line: 2558
expression: serialized.to_string()
---
keybinds clear-defaults=true {
normal {
bind "Ctrl g" { SwitchToMode "locked"; }
}
}

View file

@ -0,0 +1,130 @@
---
source: zellij-utils/src/kdl/mod.rs
assertion_line: 2731
expression: serialized.to_string()
---
keybinds clear-defaults=true {
normal {
bind "Ctrl a" { Quit; }
bind "Ctrl Alt a" { Search "up"; }
bind "Alt a" { ToggleActiveSyncTab; }
bind "Ctrl b" { Write 102 111 111; }
bind "Ctrl Alt b" { SearchToggleOption "CaseSensitivity"; }
bind "Alt b" { NewPane "right"; }
bind "Ctrl c" { WriteChars "hi there!"; }
bind "Ctrl Alt c" { ToggleMouseMode; }
bind "Alt c" { TogglePaneEmbedOrFloating; }
bind "Ctrl d" { SwitchToMode "locked"; }
bind "Ctrl Alt d" { PreviousSwapLayout; }
bind "Alt d" { ToggleFloatingPanes; }
bind "Ctrl e" { Resize "Increase"; }
bind "Ctrl Alt e" { NextSwapLayout; }
bind "Alt e" { CloseFocus; }
bind "Ctrl f" { FocusNextPane; }
bind "Alt f" { PaneNameInput 0; }
bind "Ctrl g" { FocusPreviousPane; }
bind "Ctrl Alt g" { BreakPane; }
bind "Alt g" { UndoRenamePane; }
bind "Ctrl h" { SwitchFocus; }
bind "Ctrl Alt h" { BreakPaneRight; }
bind "Alt h" { NewTab; }
bind "Ctrl i" { MoveFocus "right"; }
bind "Ctrl Alt i" { BreakPaneLeft; }
bind "Alt i" { GoToNextTab; }
bind "Ctrl j" { MoveFocusOrTab "right"; }
bind "Ctrl Alt j" {
MessagePlugin "zellij:session-manager" {
name "message_name"
cwd "/tmp"
payload "message_payload"
launch_new true
skip_cache true
floating true
title "plugin_title"
config_key_1 "config_value_1"
config_key_2 "config_value_2"
}
}
bind "Alt j" { GoToPreviousTab; }
bind "Ctrl k" { MovePane "right"; }
bind "Alt k" { CloseTab; }
bind "Ctrl l" { MovePaneBackwards; }
bind "Alt l" { GoToTab 1; }
bind "Ctrl m" { Resize "Decrease down"; }
bind "Alt m" { ToggleTab; }
bind "Ctrl n" { DumpScreen "/tmp/dumped"; }
bind "Alt n" { TabNameInput 0; }
bind "Ctrl o" { DumpLayout; }
bind "Alt o" { UndoRenameTab; }
bind "Ctrl p" { EditScrollback; }
bind "Alt p" { MoveTab "right"; }
bind "Ctrl q" { ScrollUp; }
bind "Alt q" {
Run "ls" "-l" {
cwd "/tmp"
name "my cool pane"
}
}
bind "Ctrl r" { ScrollDown; }
bind "Alt r" {
Run "ls" "-l" {
floating true
cwd "/tmp"
name "my cool pane"
}
}
bind "Ctrl s" { ScrollToBottom; }
bind "Alt s" {
Run "ls" "-l" {
in_place true
cwd "/tmp"
name "my cool pane"
}
}
bind "Ctrl t" { ScrollToTop; }
bind "Alt t" { Detach; }
bind "Ctrl u" { PageScrollUp; }
bind "Alt u" {
LaunchOrFocusPlugin "zellij:session-manager" {
floating true
move_to_focused_tab true
skip_plugin_cache true
config_key_1 "config_value_1"
config_key_2 "config_value_2"
}
}
bind "Ctrl v" { PageScrollDown; }
bind "Alt v" {
LaunchOrFocusPlugin "zellij:session-manager" {
move_to_focused_tab true
in_place true
skip_plugin_cache true
config_key_1 "config_value_1"
config_key_2 "config_value_2"
}
}
bind "Ctrl w" { HalfPageScrollUp; }
bind "Alt w" {
LaunchPlugin "zellij:session-manager" {
floating true
skip_plugin_cache true
config_key_1 "config_value_1"
config_key_2 "config_value_2"
}
}
bind "Ctrl x" { HalfPageScrollDown; }
bind "Alt x" {
LaunchPlugin "zellij:session-manager" {
in_place true
skip_plugin_cache true
config_key_1 "config_value_1"
config_key_2 "config_value_2"
}
}
bind "Ctrl y" { ToggleFocusFullscreen; }
bind "Alt y" { Copy; }
bind "Ctrl z" { TogglePaneFrames; }
bind "Alt z" { SearchInput 0; }
}
}

View file

@ -0,0 +1,11 @@
---
source: zellij-utils/src/kdl/mod.rs
assertion_line: 2576
expression: serialized.to_string()
---
keybinds clear-defaults=true {
normal {
bind "Ctrl n" { NewPane; SwitchToMode "locked"; }
}
}

View file

@ -0,0 +1,25 @@
---
source: zellij-utils/src/kdl/mod.rs
assertion_line: 2788
expression: serialized.to_string()
---
keybinds clear-defaults=true {
shared {
bind "Ctrl n" {
NewPane
SwitchToMode "locked"
MessagePlugin "zellij:session-manager" {
name "message_name"
cwd "/tmp"
payload "message_payload"
launch_new true
skip_cache true
floating true
title "plugin_title"
config_key_1 "config_value_1"
config_key_2 "config_value_2"
}
}
}
}

View file

@ -0,0 +1,17 @@
---
source: zellij-utils/src/kdl/mod.rs
assertion_line: 2757
expression: serialized.to_string()
---
keybinds clear-defaults=true {
shared_among "normal" "locked" {
bind "Ctrl n" { NewPane; SwitchToMode "locked"; }
}
shared_except "locked" "pane" {
bind "Ctrl f" { TogglePaneEmbedOrFloating; }
}
shared_among "locked" "pane" {
bind "Ctrl p" { WriteChars "foo"; }
}
}

View file

@ -0,0 +1,11 @@
---
source: zellij-utils/src/kdl/mod.rs
assertion_line: 2575
expression: serialized.to_string()
---
keybinds {
normal {
bind "Ctrl g" { SwitchToMode "locked"; }
}
}

View file

@ -0,0 +1,18 @@
---
source: zellij-utils/src/kdl/mod.rs
assertion_line: 3874
expression: serialized.to_string()
---
plugins {
compact-bar location="zellij:compact-bar"
filepicker location="zellij:strider" {
cwd "/"
}
session-manager location="zellij:session-manager"
status-bar location="zellij:status-bar"
strider location="zellij:strider"
tab-bar location="zellij:tab-bar"
welcome-screen location="zellij:session-manager" {
welcome_screen true
}
}

View file

@ -0,0 +1,11 @@
---
source: zellij-utils/src/kdl/mod.rs
assertion_line: 3891
expression: serialized.to_string()
---
plugins {
filepicker location="file:/path/to/my/plugin.wasm" {
cwd "/"
}
tab-bar location="https://foo.com/plugin.wasm"
}

View file

@ -0,0 +1,20 @@
---
source: zellij-utils/src/kdl/mod.rs
assertion_line: 3697
expression: serialized.to_string()
---
themes {
dracula {
fg 248 248 242
bg 40 42 54
red 255 85 85
green 80 250 123
yellow 241 250 140
blue 98 114 164
magenta 255 121 198
orange 255 184 108
cyan 139 233 253
black 0 0 0
white 255 255 255
}
}

View file

@ -0,0 +1,20 @@
---
source: zellij-utils/src/kdl/mod.rs
assertion_line: 3775
expression: serialized.to_string()
---
themes {
default {
fg 1
bg 10
red 30
green 40
yellow 50
blue 60
magenta 70
orange 208 135 112
cyan 80
black 20
white 255 255 255
}
}

View file

@ -0,0 +1,20 @@
---
source: zellij-utils/src/kdl/mod.rs
assertion_line: 3749
expression: serialized.to_string()
---
themes {
default {
fg 1
bg 10
red 30
green 40
yellow 50
blue 60
magenta 70
orange 254
cyan 80
black 20
white 90
}
}

View file

@ -0,0 +1,20 @@
---
source: zellij-utils/src/kdl/mod.rs
assertion_line: 3723
expression: serialized.to_string()
---
themes {
nord {
fg 216 222 233
bg 46 52 64
red 191 97 106
green 163 190 140
yellow 235 203 139
blue 129 161 193
magenta 180 142 173
orange 208 135 112
cyan 136 192 208
black 59 66 82
white 229 233 240
}
}

View file

@ -0,0 +1,33 @@
---
source: zellij-utils/src/kdl/mod.rs
assertion_line: 4821
expression: serialized.to_string()
---
themes {
dracula {
fg 248 248 242
bg 40 42 54
red 255 85 85
green 80 250 123
yellow 241 250 140
blue 98 114 164
magenta 255 121 198
orange 255 184 108
cyan 139 233 253
black 0 0 0
white 255 255 255
}
nord {
fg 216 222 233
bg 46 52 64
red 191 97 106
green 163 190 140
yellow 235 203 139
blue 129 161 193
magenta 180 142 173
orange 208 135 112
cyan 136 192 208
black 59 66 82
white 229 233 240
}
}

View file

@ -0,0 +1,11 @@
---
source: zellij-utils/src/kdl/mod.rs
assertion_line: 3932
expression: serialized.to_string()
---
ui {
pane_frames {
rounded_corners true
hide_session_name true
}
}

View file

@ -48,6 +48,7 @@ enum EventType {
EditPaneOpened = 22;
EditPaneExited = 23;
CommandPaneReRun = 24;
FailedToWriteConfigToDisk = 25;
}
message EventNameList {
@ -77,9 +78,14 @@ message Event {
EditPaneOpenedPayload edit_pane_opened_payload = 19;
EditPaneExitedPayload edit_pane_exited_payload = 20;
CommandPaneReRunPayload command_pane_rerun_payload = 21;
FailedToWriteConfigToDiskPayload failed_to_write_config_to_disk_payload = 22;
}
}
message FailedToWriteConfigToDiskPayload {
optional string file_path = 1;
}
message CommandPaneReRunPayload {
uint32 terminal_pane_id = 1;
repeated ContextItem context = 3;

View file

@ -312,6 +312,14 @@ impl TryFrom<ProtobufEvent> for Event {
},
_ => Err("Malformed payload for the CommandPaneReRun Event"),
},
Some(ProtobufEventType::FailedToWriteConfigToDisk) => match protobuf_event.payload {
Some(ProtobufEventPayload::FailedToWriteConfigToDiskPayload(
failed_to_write_configuration_payload,
)) => Ok(Event::FailedToWriteConfigToDisk(
failed_to_write_configuration_payload.file_path,
)),
_ => Err("Malformed payload for the FailedToWriteConfigToDisk Event"),
},
None => Err("Unknown Protobuf Event"),
}
}
@ -620,6 +628,12 @@ impl TryFrom<Event> for ProtobufEvent {
)),
})
},
Event::FailedToWriteConfigToDisk(file_path) => Ok(ProtobufEvent {
name: ProtobufEventType::FailedToWriteConfigToDisk as i32,
payload: Some(event::Payload::FailedToWriteConfigToDiskPayload(
FailedToWriteConfigToDiskPayload { file_path },
)),
}),
}
}
}
@ -1129,6 +1143,7 @@ impl TryFrom<ProtobufEventType> for EventType {
ProtobufEventType::EditPaneOpened => EventType::EditPaneOpened,
ProtobufEventType::EditPaneExited => EventType::EditPaneExited,
ProtobufEventType::CommandPaneReRun => EventType::CommandPaneReRun,
ProtobufEventType::FailedToWriteConfigToDisk => EventType::FailedToWriteConfigToDisk,
})
}
}
@ -1162,6 +1177,7 @@ impl TryFrom<EventType> for ProtobufEventType {
EventType::EditPaneOpened => ProtobufEventType::EditPaneOpened,
EventType::EditPaneExited => ProtobufEventType::EditPaneExited,
EventType::CommandPaneReRun => ProtobufEventType::CommandPaneReRun,
EventType::FailedToWriteConfigToDisk => ProtobufEventType::FailedToWriteConfigToDisk,
})
}
}

View file

@ -160,7 +160,7 @@ message PluginCommand {
KillSessionsPayload kill_sessions_payload = 60;
string scan_host_folder_payload = 61;
NewTabsWithLayoutInfoPayload new_tabs_with_layout_info_payload = 62;
string reconfigure_payload = 63;
ReconfigurePayload reconfigure_payload = 63;
HidePaneWithIdPayload hide_pane_with_id_payload = 64;
ShowPaneWithIdPayload show_pane_with_id_payload = 65;
OpenCommandPanePayload open_command_pane_background_payload = 66;
@ -168,6 +168,11 @@ message PluginCommand {
}
}
message ReconfigurePayload {
string config = 1;
bool write_to_disk = 2;
}
message RerunCommandPanePayload {
uint32 terminal_pane_id = 1;
}

View file

@ -11,9 +11,9 @@ pub use super::generated_api::api::{
MovePayload, NewPluginArgs as ProtobufNewPluginArgs, NewTabsWithLayoutInfoPayload,
OpenCommandPanePayload, OpenFilePayload, PaneId as ProtobufPaneId,
PaneType as ProtobufPaneType, PluginCommand as ProtobufPluginCommand, PluginMessagePayload,
RequestPluginPermissionPayload, RerunCommandPanePayload, ResizePayload, RunCommandPayload,
SetTimeoutPayload, ShowPaneWithIdPayload, SubscribePayload, SwitchSessionPayload,
SwitchTabToPayload, UnsubscribePayload, WebRequestPayload,
ReconfigurePayload, RequestPluginPermissionPayload, RerunCommandPanePayload, ResizePayload,
RunCommandPayload, SetTimeoutPayload, ShowPaneWithIdPayload, SubscribePayload,
SwitchSessionPayload, SwitchTabToPayload, UnsubscribePayload, WebRequestPayload,
},
plugin_permission::PermissionType as ProtobufPermissionType,
resize::ResizeAction as ProtobufResizeAction,
@ -935,7 +935,10 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
},
Some(CommandName::Reconfigure) => match protobuf_plugin_command.payload {
Some(Payload::ReconfigurePayload(reconfigure_payload)) => {
Ok(PluginCommand::Reconfigure(reconfigure_payload))
Ok(PluginCommand::Reconfigure(
reconfigure_payload.config,
reconfigure_payload.write_to_disk,
))
},
_ => Err("Mismatched payload for Reconfigure"),
},
@ -1558,9 +1561,12 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
)),
})
},
PluginCommand::Reconfigure(reconfigure_payload) => Ok(ProtobufPluginCommand {
PluginCommand::Reconfigure(config, write_to_disk) => Ok(ProtobufPluginCommand {
name: CommandName::Reconfigure as i32,
payload: Some(Payload::ReconfigurePayload(reconfigure_payload)),
payload: Some(Payload::ReconfigurePayload(ReconfigurePayload {
config,
write_to_disk,
})),
}),
PluginCommand::HidePaneWithId(pane_id_to_hide) => Ok(ProtobufPluginCommand {
name: CommandName::HidePaneWithId as i32,

View file

@ -74,7 +74,8 @@ fn get_default_themes() -> Themes {
let mut themes = Themes::default();
for file in ZELLIJ_DEFAULT_THEMES.files() {
if let Some(content) = file.contents_utf8() {
match Themes::from_string(&content.to_string()) {
let sourced_from_external_file = true;
match Themes::from_string(&content.to_string(), sourced_from_external_file) {
Ok(theme) => themes = themes.merge(theme),
Err(_) => {},
}

View file

@ -1,6 +1,6 @@
---
source: zellij-utils/src/setup.rs
assertion_line: 839
assertion_line: 840
expression: "format!(\"{:#?}\", config)"
---
Config {
@ -5671,6 +5671,7 @@ Config {
0,
),
},
sourced_from_external_file: false,
},
"theme-from-config": Theme {
palette: Palette {
@ -5772,6 +5773,7 @@ Config {
0,
),
},
sourced_from_external_file: false,
},
"theme-from-layout": Theme {
palette: Palette {
@ -5873,6 +5875,7 @@ Config {
0,
),
},
sourced_from_external_file: false,
},
},
plugins: PluginAliases {