aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2025-08-28 17:48:34 +0200
committerGitHub <noreply@github.com>2025-08-28 17:48:34 +0200
commit3a26df6314cf5966ffb3065b97d4183b480d1441 (patch)
tree0afc4bc62272f22c397ccab260a46bbfed4ac24f
parentfix(web): avoid sending single meta keypresses, cleanup (#4376) (diff)
downloadsl0ppij-3a26df6314cf5966ffb3065b97d4183b480d1441.tar.gz
sl0ppij-3a26df6314cf5966ffb3065b97d4183b480d1441.tar.xz
sl0ppij-3a26df6314cf5966ffb3065b97d4183b480d1441.zip
refactor(client-server-contract): config + layout (#4383)
* commented out ConfigWrittenToDisk instructions * commented out WriteConfigToDisk instructions * commented out FailedToWriteConfigToDisk and WriteConfigToDisk instructions * pass cli assets from client to server instead of parsed config - kind of working, lots of stuff still broken * move ClientAttributes to the server side * move ClientAttributes to the server side in Attach instruction as well * move CliArgs and runtime options from NewClient -> move them to the server * moved plugin_aliases to the server side * moved initial config writing to the server side * moved layout parsing to the server * some cleanups * move config file watching and live reload to the server * move config file writing to the server side * use saved config when attaching * get web server to work with the new config roles * some cleanups * moar cleanups * even moar cleanups * yet more cleanups * fix tests * style(fmt): rustfmt * style(fmt): rustfmt * fix e2e tests * docs(changelog): add PR
-rw-r--r--CHANGELOG.md1
-rw-r--r--Cargo.lock1
-rw-r--r--src/commands.rs116
-rw-r--r--src/tests/e2e/remote_runner.rs2
-rw-r--r--zellij-client/src/lib.rs326
-rw-r--r--zellij-client/src/web_client/ipc_listener.rs64
-rw-r--r--zellij-client/src/web_client/mod.rs43
-rw-r--r--zellij-client/src/web_client/server_listener.rs76
-rw-r--r--zellij-client/src/web_client/session_management.rs222
-rw-r--r--zellij-client/src/web_client/types.rs15
-rw-r--r--zellij-client/src/web_client/unit/web_client_tests.rs28
-rw-r--r--zellij-server/Cargo.toml1
-rw-r--r--zellij-server/src/lib.rs368
-rw-r--r--zellij-server/src/plugins/mod.rs2
-rw-r--r--zellij-server/src/plugins/unit/plugin_tests.rs8
-rw-r--r--zellij-server/src/route.rs41
-rw-r--r--zellij-server/src/unit/screen_tests.rs2
-rw-r--r--zellij-utils/src/cli.rs23
-rw-r--r--zellij-utils/src/data.rs17
-rw-r--r--zellij-utils/src/errors.rs1
-rw-r--r--zellij-utils/src/input/cli_assets.rs86
-rw-r--r--zellij-utils/src/input/config.rs142
-rw-r--r--zellij-utils/src/input/layout.rs58
-rw-r--r--zellij-utils/src/input/mod.rs1
-rw-r--r--zellij-utils/src/input/options.rs70
-rw-r--r--zellij-utils/src/ipc.rs24
-rw-r--r--zellij-utils/src/setup.rs20
-rw-r--r--zellij-utils/src/web_server_commands.rs2
28 files changed, 882 insertions, 878 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5f31d71a..1aa388aa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
## [Unreleased]
* fix: meta key handling in web client (https://github.com/zellij-org/zellij/pull/4376)
+* refactor: move logical structures from client to server (https://github.com/zellij-org/zellij/pull/4383)
## [0.43.1] - 2025-08-08
* fix: pane rename backspace regression (https://github.com/zellij-org/zellij/pull/4346)
diff --git a/Cargo.lock b/Cargo.lock
index d0beddc6..4e29e892 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5853,6 +5853,7 @@ dependencies = [
"sixel-tokenizer",
"sysinfo",
"tempfile",
+ "tokio",
"typetag",
"unicode-width 0.1.10",
"url",
diff --git a/src/commands.rs b/src/commands.rs
index e25bce6d..01df6612 100644
--- a/src/commands.rs
+++ b/src/commands.rs
@@ -21,6 +21,8 @@ use zellij_utils::sessions::{
SessionNameMatch,
};
+use zellij_utils::consts::session_layout_cache_file_name;
+
#[cfg(feature = "web_server_capability")]
use zellij_client::web_client::start_web_client as start_web_client_impl;
@@ -36,15 +38,14 @@ use miette::{Report, Result};
use zellij_server::{os_input_output::get_server_os_input, start_server as start_server_impl};
use zellij_utils::{
cli::{CliArgs, Command, SessionCommand, Sessions},
- data::{ConnectToSession, LayoutInfo},
+ data::ConnectToSession,
envs,
input::{
actions::Action,
config::{Config, ConfigError},
- layout::Layout,
options::Options,
},
- setup::{find_default_config_dir, get_layout_dir, Setup},
+ setup::Setup,
};
pub(crate) use zellij_utils::sessions::list_sessions;
@@ -213,7 +214,7 @@ pub(crate) fn start_web_server(
}
fn create_new_client() -> ClientInfo {
- ClientInfo::New(generate_unique_session_name_or_exit())
+ ClientInfo::New(generate_unique_session_name_or_exit(), None, None)
}
#[cfg(feature = "web_server_capability")]
@@ -534,7 +535,7 @@ fn attach_with_session_name(
if session_exists(session).unwrap() {
ClientInfo::Attach(session_name.unwrap(), config_options)
} else {
- ClientInfo::New(session_name.unwrap())
+ ClientInfo::New(session_name.unwrap(), None, None)
}
},
Some(prefix) => match match_session_name(prefix).unwrap() {
@@ -583,7 +584,7 @@ pub(crate) fn start_client(opts: CliArgs) {
convert_old_yaml_files(&opts);
let (
config,
- layout,
+ _layout,
config_options,
mut config_without_layout,
mut config_options_without_layout,
@@ -599,19 +600,18 @@ pub(crate) fn start_client(opts: CliArgs) {
process::exit(1);
},
};
- let layout_is_welcome_screen = opts.layout == Some(PathBuf::from("welcome"))
- || config.options.default_layout == Some(PathBuf::from("welcome"));
let mut reconnect_to_session: Option<ConnectToSession> = None;
let os_input = get_os_input(get_client_os_input);
loop {
let os_input = os_input.clone();
- let mut config = config.clone();
- let mut layout = layout.clone();
+ let config = config.clone();
let mut config_options = config_options.clone();
let mut opts = opts.clone();
let mut is_a_reconnect = false;
let mut should_create_detached = false;
+ let mut layout_info = None;
+ let mut new_session_cwd = None;
if let Some(reconnect_to_session) = &reconnect_to_session {
// this is integration code to make session reconnects work with this existing,
@@ -639,53 +639,11 @@ pub(crate) fn start_client(opts: CliArgs) {
}
if let Some(reconnect_layout) = &reconnect_to_session.layout {
- let layout_dir = config.options.layout_dir.clone().or_else(|| {
- get_layout_dir(opts.config_dir.clone().or_else(find_default_config_dir))
- });
- let new_session_layout = match reconnect_layout {
- LayoutInfo::BuiltIn(layout_name) => Layout::from_default_assets(
- &PathBuf::from(layout_name),
- layout_dir.clone(),
- config_without_layout.clone(),
- ),
- LayoutInfo::File(layout_name) => Layout::from_path_or_default(
- Some(&PathBuf::from(layout_name)),
- layout_dir.clone(),
- config_without_layout.clone(),
- ),
- LayoutInfo::Url(url) => Layout::from_url(&url, config_without_layout.clone()),
- LayoutInfo::Stringified(stringified_layout) => Layout::from_stringified_layout(
- &stringified_layout,
- config_without_layout.clone(),
- ),
- };
- match new_session_layout {
- Ok(new_session_layout) => {
- // here we make sure to override both the layout and the config, but we do
- // this with an instance of the config before it was merged with the
- // layout configuration of the previous iteration of the loop, since we do
- // not want it to mix with the config of this session
- let (new_layout, new_layout_config) = new_session_layout;
- layout = new_layout;
- if let Some(cwd) = reconnect_to_session.cwd.as_ref() {
- layout.add_cwd_to_layout(cwd);
- }
- let mut new_config = config_without_layout.clone();
- let _ = new_config.merge(new_layout_config.clone());
- config = new_config;
- config_options =
- config_options_without_layout.merge(new_layout_config.options);
- },
- Err(e) => {
- log::error!("Failed to parse new session layout: {:?}", e);
- },
- }
- } else {
- if let Some(cwd) = reconnect_to_session.cwd.as_ref() {
- config_options.default_cwd = Some(cwd.clone());
- }
+ layout_info = Some(reconnect_layout.clone());
+ }
+ if let Some(cwd) = &reconnect_to_session.cwd {
+ new_session_cwd = Some(cwd.clone());
}
-
is_a_reconnect = true;
}
@@ -710,7 +668,7 @@ pub(crate) fn start_client(opts: CliArgs) {
};
should_create_detached = create_background;
- let client = if let Some(idx) = index {
+ let mut client = if let Some(idx) = index {
attach_with_session_index(
config_options.clone(),
idx,
@@ -742,7 +700,12 @@ pub(crate) fn start_client(opts: CliArgs) {
if force_run_commands {
resurrection_layout.recursively_add_start_suspended(Some(false));
}
- ClientInfo::Resurrect(session_name.clone(), resurrection_layout)
+ ClientInfo::Resurrect(
+ session_name.clone(),
+ session_layout_cache_file_name(session_name.as_ref()),
+ force_run_commands,
+ new_session_cwd.clone(),
+ )
},
_ => attach_with_session_name(
session_name,
@@ -758,13 +721,13 @@ pub(crate) fn start_client(opts: CliArgs) {
}
}
- let attach_layout = match &client {
- ClientInfo::Attach(_, _) => None,
- ClientInfo::New(_) => Some(layout),
- ClientInfo::Resurrect(_session_name, layout_to_resurrect) => {
- Some(layout_to_resurrect.clone())
- },
- };
+ if let Some(layout_info) = layout_info {
+ client.set_layout_info(layout_info);
+ }
+
+ if let Some(new_session_cwd) = new_session_cwd {
+ client.set_cwd(new_session_cwd);
+ }
let tab_position_to_focus = reconnect_to_session
.as_ref()
@@ -778,12 +741,10 @@ pub(crate) fn start_client(opts: CliArgs) {
config,
config_options,
client,
- attach_layout,
tab_position_to_focus,
pane_id_to_focus,
is_a_reconnect,
should_create_detached,
- layout_is_welcome_screen,
);
} else {
if let Some(session_name) = opts.session.clone() {
@@ -793,13 +754,11 @@ pub(crate) fn start_client(opts: CliArgs) {
opts,
config,
config_options,
- ClientInfo::New(session_name),
- Some(layout),
+ ClientInfo::New(session_name, layout_info, new_session_cwd),
None,
None,
is_a_reconnect,
should_create_detached,
- layout_is_welcome_screen,
);
} else {
if let Some(session_name) = config_options.session_name.as_ref() {
@@ -823,25 +782,16 @@ pub(crate) fn start_client(opts: CliArgs) {
config_options.clone(),
true,
);
- let attach_layout = match &client {
- ClientInfo::Attach(_, _) => None,
- ClientInfo::New(_) => Some(layout),
- ClientInfo::Resurrect(_, resurrection_layout) => {
- Some(resurrection_layout.clone())
- },
- };
reconnect_to_session = start_client_impl(
Box::new(os_input),
opts,
config,
config_options,
client,
- attach_layout,
None,
None,
is_a_reconnect,
should_create_detached,
- layout_is_welcome_screen,
);
},
_ => {
@@ -851,13 +801,11 @@ pub(crate) fn start_client(opts: CliArgs) {
opts,
config,
config_options.clone(),
- ClientInfo::New(session_name.clone()),
- Some(layout),
+ ClientInfo::New(session_name.clone(), layout_info, new_session_cwd),
None,
None,
is_a_reconnect,
should_create_detached,
- layout_is_welcome_screen,
);
},
}
@@ -876,13 +824,11 @@ pub(crate) fn start_client(opts: CliArgs) {
opts,
config,
config_options,
- ClientInfo::New(session_name),
- Some(layout),
+ ClientInfo::New(session_name, layout_info, new_session_cwd),
None,
None,
is_a_reconnect,
should_create_detached,
- layout_is_welcome_screen,
);
}
}
diff --git a/src/tests/e2e/remote_runner.rs b/src/tests/e2e/remote_runner.rs
index b9035002..54b6c468 100644
--- a/src/tests/e2e/remote_runner.rs
+++ b/src/tests/e2e/remote_runner.rs
@@ -185,7 +185,7 @@ fn start_zellij_without_frames(channel: &mut ssh2::Channel) {
channel
.write_all(
format!(
- "{} {} --session {} --data-dir {} options --show-release-notes false --show-startup-tips false --no-pane-frames\n",
+ "{} {} --session {} --data-dir {} options --show-release-notes false --show-startup-tips false --pane-frames false\n",
SET_ENV_VARIABLES, ZELLIJ_EXECUTABLE_LOCATION, SESSION_NAME, ZELLIJ_DATA_DIR
)
.as_bytes(),
diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs
index 0f326f8f..f06663d0 100644
--- a/zellij-client/src/lib.rs
+++ b/zellij-client/src/lib.rs
@@ -14,7 +14,7 @@ use log::info;
use std::env::current_exe;
use std::io::{self, Write};
use std::net::{IpAddr, Ipv4Addr};
-use std::path::Path;
+use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::{Arc, Mutex};
use std::thread;
@@ -27,20 +27,17 @@ use crate::{
os_input_output::ClientOsApi, stdin_handler::stdin_loop,
};
use termwiz::input::InputEvent;
+use zellij_utils::cli::CliArgs;
use zellij_utils::{
channels::{self, ChannelWithContext, SenderWithContext},
consts::{set_permissions, ZELLIJ_SOCK_DIR},
- data::{ClientId, ConnectToSession, KeyWithModifier, Style},
+ data::{ClientId, ConnectToSession, KeyWithModifier, LayoutInfo},
envs,
errors::{ClientContext, ContextType, ErrorInstruction},
- input::{
- config::{watch_config_file_changes, Config},
- options::Options,
- },
- ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg},
+ input::{cli_assets::CliAssets, config::Config, options::Options},
+ ipc::{ClientToServerMsg, ExitReason, ServerToClientMsg},
pane_size::Size,
};
-use zellij_utils::{cli::CliArgs, input::layout::Layout};
/// Instructions related to the client-side application
#[derive(Debug, Clone)]
@@ -59,12 +56,10 @@ pub(crate) enum ClientInstruction {
UnblockCliPipeInput(()), // String -> pipe name
CliPipeOutput((), ()), // String -> pipe name, String -> output
QueryTerminalSize,
- WriteConfigToDisk {
- config: String,
- },
StartWebServer,
#[allow(dead_code)] // we need the session name here even though we're not currently using it
RenamedSession(String), // String -> new session name
+ ConfigFileUpdated,
}
impl From<ServerToClientMsg> for ClientInstruction {
@@ -86,11 +81,9 @@ impl From<ServerToClientMsg> for ClientInstruction {
ClientInstruction::CliPipeOutput((), ())
},
ServerToClientMsg::QueryTerminalSize => ClientInstruction::QueryTerminalSize,
- ServerToClientMsg::WriteConfigToDisk { config } => {
- ClientInstruction::WriteConfigToDisk { config }
- },
ServerToClientMsg::StartWebServer => ClientInstruction::StartWebServer,
ServerToClientMsg::RenamedSession(name) => ClientInstruction::RenamedSession(name),
+ ServerToClientMsg::ConfigFileUpdated => ClientInstruction::ConfigFileUpdated,
}
}
}
@@ -112,9 +105,9 @@ impl From<&ClientInstruction> for ClientContext {
ClientInstruction::UnblockCliPipeInput(..) => ClientContext::UnblockCliPipeInput,
ClientInstruction::CliPipeOutput(..) => ClientContext::CliPipeOutput,
ClientInstruction::QueryTerminalSize => ClientContext::QueryTerminalSize,
- ClientInstruction::WriteConfigToDisk { .. } => ClientContext::WriteConfigToDisk,
ClientInstruction::StartWebServer => ClientContext::StartWebServer,
ClientInstruction::RenamedSession(..) => ClientContext::RenamedSession,
+ ClientInstruction::ConfigFileUpdated => ClientContext::ConfigFileUpdated,
}
}
}
@@ -126,9 +119,9 @@ impl ErrorInstruction for ClientInstruction {
}
#[cfg(feature = "web_server_capability")]
-fn spawn_web_server(opts: &CliArgs) -> Result<String, String> {
+fn spawn_web_server(cli_args: &CliArgs) -> Result<String, String> {
let mut cmd = Command::new(current_exe().map_err(|e| e.to_string())?);
- if let Some(config_file_path) = Config::config_file_path(opts) {
+ if let Some(config_file_path) = Config::config_file_path(cli_args) {
let config_file_path_exists = Path::new(&config_file_path).exists();
if !config_file_path_exists {
return Err(format!(
@@ -157,7 +150,7 @@ fn spawn_web_server(opts: &CliArgs) -> Result<String, String> {
}
#[cfg(not(feature = "web_server_capability"))]
-fn spawn_web_server(_opts: &CliArgs) -> Result<String, String> {
+fn spawn_web_server(_cli_args: &CliArgs) -> Result<String, String> {
log::error!(
"This version of Zellij was compiled without web server support, cannot run web server!"
);
@@ -188,16 +181,29 @@ pub fn spawn_server(socket_path: &Path, debug: bool) -> io::Result<()> {
#[derive(Debug, Clone)]
pub enum ClientInfo {
Attach(String, Options),
- New(String),
- Resurrect(String, Layout),
+ New(String, Option<LayoutInfo>, Option<PathBuf>), // PathBuf -> explicit cwd
+ Resurrect(String, PathBuf, bool, Option<PathBuf>), // (name, path_to_layout, force_run_commands, cwd)
}
impl ClientInfo {
pub fn get_session_name(&self) -> &str {
match self {
Self::Attach(ref name, _) => name,
- Self::New(ref name) => name,
- Self::Resurrect(ref name, _) => name,
+ Self::New(ref name, _layout_info, _layout_cwd) => name,
+ Self::Resurrect(ref name, _, _, _) => name,
+ }
+ }
+ pub fn set_layout_info(&mut self, new_layout_info: LayoutInfo) {
+ match self {
+ ClientInfo::New(_, layout_info, _) => *layout_info = Some(new_layout_info),
+ _ => {},
+ }
+ }
+ pub fn set_cwd(&mut self, new_cwd: PathBuf) {
+ match self {
+ ClientInfo::New(_, _, cwd) => *cwd = Some(new_cwd),
+ ClientInfo::Resurrect(_, _, _, cwd) => *cwd = Some(new_cwd),
+ _ => {},
}
}
}
@@ -214,19 +220,17 @@ pub(crate) enum InputInstruction {
pub fn start_client(
mut os_input: Box<dyn ClientOsApi>,
- opts: CliArgs,
+ cli_args: CliArgs,
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>,
pane_id_to_focus: Option<(u32, bool)>, // (pane_id, is_plugin)
is_a_reconnect: bool,
start_detached_and_exit: bool,
- layout_is_welcome_screen: bool,
) -> Option<ConnectToSession> {
if start_detached_and_exit {
- start_server_detached(os_input, opts, config, config_options, info, layout);
+ start_server_detached(os_input, cli_args, config, config_options, info);
return None;
}
info!("Starting Zellij client!");
@@ -265,19 +269,8 @@ pub fn start_client(
envs::set_zellij("0".to_string());
config.env.set_vars();
- let palette = config
- .theme_config(config_options.theme.as_ref())
- .unwrap_or_else(|| os_input.load_palette().into());
-
let full_screen_ws = os_input.get_terminal_size_using_fd(0);
- let client_attributes = ClientAttributes {
- size: full_screen_ws,
- style: Style {
- colors: palette,
- rounded_corners: config.ui.pane_frames.rounded_corners,
- hide_session_name: config.ui.pane_frames.hide_session_name,
- },
- };
+
let web_server_ip = config_options
.web_server_ip
.unwrap_or_else(|| IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
@@ -301,11 +294,33 @@ pub fn start_client(
let ipc_pipe = create_ipc_pipe();
let is_web_client = false;
+ let cli_assets = CliAssets {
+ config_file_path: Config::config_file_path(&cli_args),
+ config_dir: cli_args.config_dir.clone(),
+ should_ignore_config: cli_args.is_setup_clean(),
+ configuration_options: Some(config_options.clone()),
+ layout: cli_args
+ .layout
+ .as_ref()
+ .and_then(|l| {
+ LayoutInfo::from_config(&config_options.layout_dir, &Some(l.clone()))
+ })
+ .or_else(|| {
+ LayoutInfo::from_config(
+ &config_options.layout_dir,
+ &config_options.default_layout,
+ )
+ }),
+ terminal_window_size: full_screen_ws,
+ data_dir: cli_args.data_dir.clone(),
+ is_debug: cli_args.debug,
+ max_panes: cli_args.max_panes,
+ force_run_layout_commands: false,
+ cwd: None,
+ };
(
ClientToServerMsg::AttachClient(
- client_attributes,
- config.clone(),
- config_options.clone(),
+ cli_assets,
tab_position_to_focus,
pane_id_to_focus,
is_web_client,
@@ -313,41 +328,84 @@ pub fn start_client(
ipc_pipe,
)
},
- ClientInfo::New(name) | ClientInfo::Resurrect(name, _) => {
+ ClientInfo::Resurrect(name, path_to_layout, force_run_commands, cwd) => {
envs::set_session_name(name.clone());
+
+ let cli_assets = CliAssets {
+ config_file_path: Config::config_file_path(&cli_args),
+ config_dir: cli_args.config_dir.clone(),
+ should_ignore_config: cli_args.is_setup_clean(),
+ configuration_options: Some(config_options.clone()),
+ layout: Some(LayoutInfo::File(path_to_layout.display().to_string())),
+ terminal_window_size: full_screen_ws,
+ data_dir: cli_args.data_dir.clone(),
+ is_debug: cli_args.debug,
+ max_panes: cli_args.max_panes,
+ force_run_layout_commands: force_run_commands,
+ cwd,
+ };
+
os_input.update_session_name(name);
let ipc_pipe = create_ipc_pipe();
- spawn_server(&*ipc_pipe, opts.debug).unwrap();
+ spawn_server(&*ipc_pipe, cli_args.debug).unwrap();
if should_start_web_server {
- if let Err(e) = spawn_web_server(&opts) {
+ if let Err(e) = spawn_web_server(&cli_args) {
log::error!("Failed to start web server: {}", e);
}
}
- 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;
let is_web_client = false;
(
- ClientToServerMsg::NewClient(
- client_attributes,
- Box::new(opts.clone()),
- Box::new(config.clone()),
- Box::new(config_options.clone()),
- Box::new(layout.unwrap()),
- Box::new(config.plugins.clone()),
- is_web_client,
- should_launch_setup_wizard,
- layout_is_welcome_screen,
- ),
+ ClientToServerMsg::FirstClientConnected(cli_assets, is_web_client),
+ ipc_pipe,
+ )
+ },
+ ClientInfo::New(name, layout_info, layout_cwd) => {
+ envs::set_session_name(name.clone());
+
+ let cli_assets = CliAssets {
+ config_file_path: Config::config_file_path(&cli_args),
+ config_dir: cli_args.config_dir.clone(),
+ should_ignore_config: cli_args.is_setup_clean(),
+ configuration_options: Some(config_options.clone()),
+ layout: layout_info.or_else(|| {
+ cli_args
+ .layout
+ .as_ref()
+ .and_then(|l| {
+ LayoutInfo::from_config(&config_options.layout_dir, &Some(l.clone()))
+ })
+ .or_else(|| {
+ LayoutInfo::from_config(
+ &config_options.layout_dir,
+ &config_options.default_layout,
+ )
+ })
+ }),
+ terminal_window_size: full_screen_ws,
+ data_dir: cli_args.data_dir.clone(),
+ is_debug: cli_args.debug,
+ max_panes: cli_args.max_panes,
+ force_run_layout_commands: false,
+ cwd: layout_cwd,
+ };
+
+ os_input.update_session_name(name);
+ let ipc_pipe = create_ipc_pipe();
+
+ spawn_server(&*ipc_pipe, cli_args.debug).unwrap();
+ if should_start_web_server {
+ if let Err(e) = spawn_web_server(&cli_args) {
+ log::error!("Failed to start web server: {}", e);
+ }
+ }
+
+ let is_web_client = false;
+
+ (
+ ClientToServerMsg::FirstClientConnected(cli_assets, is_web_client),
ipc_pipe,
)
},
@@ -428,9 +486,7 @@ pub fn start_client(
.name("signal_listener".to_string())
.spawn({
let os_input = os_input.clone();
- let opts = opts.clone();
move || {
- report_changes_in_config_file(&opts, &os_input);
os_input.handle_signals(
Box::new({
let os_api = os_input.clone();
@@ -612,23 +668,6 @@ 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));
- },
- }
- },
ClientInstruction::StartWebServer => {
let web_server_base_url = web_server_base_url(
web_server_ip,
@@ -636,7 +675,7 @@ pub fn start_client(
has_certificate,
enforce_https_for_localhost,
);
- match spawn_web_server(&opts) {
+ match spawn_web_server(&cli_args) {
Ok(_) => {
let _ = os_input.send_to_server(ClientToServerMsg::WebServerStarted(
web_server_base_url,
@@ -690,31 +729,16 @@ pub fn start_client(
pub fn start_server_detached(
mut os_input: Box<dyn ClientOsApi>,
- opts: CliArgs,
+ cli_args: CliArgs,
config: Config,
config_options: Options,
info: ClientInfo,
- layout: Option<Layout>,
) {
envs::set_zellij("0".to_string());
config.env.set_vars();
let should_start_web_server = config_options.web_server.map(|w| w).unwrap_or(false);
- let palette = config
- .theme_config(config_options.theme.as_ref())
- .unwrap_or_else(|| os_input.load_palette().into());
-
- let client_attributes = ClientAttributes {
- size: Size { rows: 50, cols: 50 }, // just so size is not 0, it doesn't matter because we
- // immediately detach
- style: Style {
- colors: palette,
- rounded_corners: config.ui.pane_frames.rounded_corners,
- hide_session_name: config.ui.pane_frames.hide_session_name,
- },
- };
-
let create_ipc_pipe = || -> std::path::PathBuf {
let mut sock_dir = ZELLIJ_SOCK_DIR.clone();
std::fs::create_dir_all(&sock_dir).unwrap();
@@ -724,33 +748,85 @@ pub fn start_server_detached(
};
let (first_msg, ipc_pipe) = match info {
- ClientInfo::New(name) | ClientInfo::Resurrect(name, _) => {
+ ClientInfo::Resurrect(name, path_to_layout, force_run_commands, cwd) => {
envs::set_session_name(name.clone());
+
+ let cli_assets = CliAssets {
+ config_file_path: Config::config_file_path(&cli_args),
+ config_dir: cli_args.config_dir.clone(),
+ should_ignore_config: cli_args.is_setup_clean(),
+ configuration_options: Some(config_options.clone()),
+ layout: Some(LayoutInfo::File(path_to_layout.display().to_string())),
+ terminal_window_size: Size { cols: 50, rows: 50 }, // static number until a
+ // client connects
+ data_dir: cli_args.data_dir.clone(),
+ is_debug: cli_args.debug,
+ max_panes: cli_args.max_panes,
+ force_run_layout_commands: force_run_commands,
+ cwd,
+ };
+
os_input.update_session_name(name);
let ipc_pipe = create_ipc_pipe();
- spawn_server(&*ipc_pipe, opts.debug).unwrap();
+ spawn_server(&*ipc_pipe, cli_args.debug).unwrap();
if should_start_web_server {
- if let Err(e) = spawn_web_server(&opts) {
+ if let Err(e) = spawn_web_server(&cli_args) {
log::error!("Failed to start web server: {}", e);
}
}
- let should_launch_setup_wizard = false; // no setup wizard when starting a detached
- // server
+
let is_web_client = false;
(
- 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()),
- is_web_client,
- should_launch_setup_wizard,
- false,
- ),
+ ClientToServerMsg::FirstClientConnected(cli_assets, is_web_client),
+ ipc_pipe,
+ )
+ },
+ ClientInfo::New(name, layout_info, layout_cwd) => {
+ envs::set_session_name(name.clone());
+
+ let cli_assets = CliAssets {
+ config_file_path: Config::config_file_path(&cli_args),
+ config_dir: cli_args.config_dir.clone(),
+ should_ignore_config: cli_args.is_setup_clean(),
+ configuration_options: cli_args.options(),
+ layout: layout_info.or_else(|| {
+ cli_args
+ .layout
+ .as_ref()
+ .and_then(|l| {
+ LayoutInfo::from_config(&config_options.layout_dir, &Some(l.clone()))
+ })
+ .or_else(|| {
+ LayoutInfo::from_config(
+ &config_options.layout_dir,
+ &config_options.default_layout,
+ )
+ })
+ }),
+ terminal_window_size: Size { cols: 50, rows: 50 }, // static number until a
+ // client connects
+ data_dir: cli_args.data_dir.clone(),
+ is_debug: cli_args.debug,
+ max_panes: cli_args.max_panes,
+ force_run_layout_commands: false,
+ cwd: layout_cwd,
+ };
+
+ os_input.update_session_name(name);
+ let ipc_pipe = create_ipc_pipe();
+
+ spawn_server(&*ipc_pipe, cli_args.debug).unwrap();
+ if should_start_web_server {
+ if let Err(e) = spawn_web_server(&cli_args) {
+ log::error!("Failed to start web server: {}", e);
+ }
+ }
+ let is_web_client = false;
+
+ (
+ ClientToServerMsg::FirstClientConnected(cli_assets, is_web_client),
ipc_pipe,
)
},
@@ -763,21 +839,3 @@ pub fn start_server_detached(
os_input.connect_to_server(&*ipc_pipe);
os_input.send_to_server(first_msg);
}
-
-pub fn report_changes_in_config_file(opts: &CliArgs, os_input: &Box<dyn ClientOsApi>) {
- if let Some(config_file_path) = Config::config_file_path(&opts) {
- let os_input = os_input.clone();
- std::thread::spawn(move || {
- let rt = tokio::runtime::Runtime::new().unwrap();
- rt.block_on(async move {
- watch_config_file_changes(config_file_path, move |new_config| {
- let os_input = os_input.clone();
- async move {
- os_input.send_to_server(ClientToServerMsg::ConfigWrittenToDisk(new_config));
- }
- })
- .await;
- });
- });
- }
-}
diff --git a/zellij-client/src/web_client/ipc_listener.rs b/zellij-client/src/web_client/ipc_listener.rs
index 801be8fc..766124ad 100644
--- a/zellij-client/src/web_client/ipc_listener.rs
+++ b/zellij-client/src/web_client/ipc_listener.rs
@@ -1,11 +1,7 @@
-use crate::web_client::types::AppState;
-
-use super::control_message::{SetConfigPayload, WebServerToWebClientControlMessage};
use axum_server::Handle;
use tokio::io::AsyncReadExt;
use tokio::net::{UnixListener, UnixStream};
use zellij_utils::consts::WEBSERVER_SOCKET_PATH;
-use zellij_utils::ipc::ClientToServerMsg;
use zellij_utils::web_server_commands::InstructionForWebServer;
pub async fn create_webserver_receiver(
@@ -33,7 +29,7 @@ pub async fn receive_webserver_instruction(
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
}
-pub async fn listen_to_web_server_instructions(server_handle: Handle, state: AppState, id: &str) {
+pub async fn listen_to_web_server_instructions(server_handle: Handle, id: &str) {
loop {
let receiver = create_webserver_receiver(id).await;
match receiver {
@@ -44,64 +40,6 @@ pub async fn listen_to_web_server_instructions(server_handle: Handle, state: App
server_handle.shutdown();
break;
},
- InstructionForWebServer::ConfigWrittenToDisk(new_config) => {
- let set_config_payload = SetConfigPayload::from(&new_config);
-
- let mut config = state.config.lock().unwrap();
- *config = new_config.clone();
-
- let client_ids: Vec<String> = {
- let connection_table_lock = state.connection_table.lock().unwrap();
- connection_table_lock
- .client_id_to_channels
- .keys()
- .cloned()
- .collect()
- };
-
- let config_message =
- WebServerToWebClientControlMessage::SetConfig(set_config_payload);
- let config_msg_json = match serde_json::to_string(&config_message) {
- Ok(json) => json,
- Err(e) => {
- log::error!("Failed to serialize config message: {}", e);
- continue;
- },
- };
-
- for client_id in client_ids {
- if let Some(control_tx) = state
- .connection_table
- .lock()
- .unwrap()
- .get_client_control_tx(&client_id)
- {
- let ws_message = config_msg_json.clone();
- match control_tx.send(ws_message.into()) {
- Ok(_) => {}, // no-op
- Err(e) => {
- log::error!(
- "Failed to send config update to client {}: {}",
- client_id,
- e
- );
- },
- }
- }
- if let Some(os_input) = state
- .connection_table
- .lock()
- .unwrap()
- .get_client_os_api(&client_id)
- {
- // notify the zellij server of the config change
- os_input.send_to_server(
- ClientToServerMsg::ConfigWrittenToDisk(new_config.clone()),
- );
- }
- }
- // Continue loop to recreate receiver for next message
- },
},
Err(e) => {
log::error!("Failed to process web server instruction: {}", e);
diff --git a/zellij-client/src/web_client/mod.rs b/zellij-client/src/web_client/mod.rs
index d7ef7ec0..9d46ed51 100644
--- a/zellij-client/src/web_client/mod.rs
+++ b/zellij-client/src/web_client/mod.rs
@@ -33,10 +33,7 @@ use nix::sys::stat::{umask, Mode};
use interprocess::unnamed_pipe::pipe;
use std::io::{prelude::*, BufRead, BufReader};
use tokio::runtime::Runtime;
-use zellij_utils::input::{
- config::{watch_config_file_changes, Config},
- options::Options,
-};
+use zellij_utils::input::{config::Config, options::Options};
use authentication::auth_middleware;
use http_handlers::{
@@ -52,13 +49,6 @@ use utils::should_use_https;
use uuid::Uuid;
use websocket_handlers::{ws_handler_control, ws_handler_terminal};
-use zellij_utils::{
- consts::WEBSERVER_SOCKET_PATH,
- web_server_commands::{
- create_webserver_sender, send_webserver_instruction, InstructionForWebServer,
- },
-};
-
pub fn start_web_client(
config: Config,
config_options: Options,
@@ -170,25 +160,6 @@ pub fn start_web_client(
));
}
-#[allow(dead_code)]
-async fn listen_to_config_file_changes(config_file_path: PathBuf, instance_id: &str) {
- let socket_path = WEBSERVER_SOCKET_PATH.join(instance_id);
-
- watch_config_file_changes(config_file_path, move |new_config| {
- let socket_path = socket_path.clone();
- async move {
- if let Ok(mut sender) = create_webserver_sender(&socket_path.to_string_lossy()) {
- let _ = send_webserver_instruction(
- &mut sender,
- InstructionForWebServer::ConfigWrittenToDisk(new_config),
- );
- drop(sender);
- }
- }
- })
- .await;
-}
-
pub async fn serve_web_client(
config: Config,
config_options: Options,
@@ -219,15 +190,6 @@ pub async fn serve_web_client(
.take(5)
.collect();
- #[cfg(not(test))]
- tokio::spawn({
- let config_file_path = config_file_path.clone();
- let id_string = format!("{}", id);
- async move {
- listen_to_config_file_changes(config_file_path, &id_string).await;
- }
- });
-
let is_https = rustls_config.is_some();
let state = AppState {
connection_table: connection_table.clone(),
@@ -241,9 +203,8 @@ pub async fn serve_web_client(
tokio::spawn({
let server_handle = server_handle.clone();
- let state = state.clone();
async move {
- listen_to_web_server_instructions(server_handle, state, &format!("{}", id)).await;
+ listen_to_web_server_instructions(server_handle, &format!("{}", id)).await;
}
});
diff --git a/zellij-client/src/web_client/server_listener.rs b/zellij-client/src/web_client/server_listener.rs
index 04ad6e04..1a19c327 100644
--- a/zellij-client/src/web_client/server_listener.rs
+++ b/zellij-client/src/web_client/server_listener.rs
@@ -1,5 +1,5 @@
use crate::os_input_output::ClientOsApi;
-use crate::web_client::control_message::WebServerToWebClientControlMessage;
+use crate::web_client::control_message::{SetConfigPayload, WebServerToWebClientControlMessage};
use crate::web_client::session_management::build_initial_connection;
use crate::web_client::types::{ClientConnectionBus, ConnectionTable, SessionManager};
use crate::web_client::utils::terminal_init_messages;
@@ -33,7 +33,7 @@ pub fn zellij_server_listener(
move || {
let mut client_connection_bus =
ClientConnectionBus::new(&web_client_id, &connection_table);
- let (mut reconnect_to_session, is_welcome_screen) =
+ let mut reconnect_to_session =
match build_initial_connection(session_name, &config) {
Ok(initial_session_connection) => initial_session_connection,
Err(e) => {
@@ -81,17 +81,13 @@ pub fn zellij_server_listener(
.unwrap()
.to_owned();
- let is_web_client = true;
let (first_message, zellij_ipc_pipe) = session_manager.spawn_session_if_needed(
&session_name,
- path,
client_attributes,
- &config,
+ config_file_path.clone(),
&config_options,
- is_web_client,
os_input.clone(),
reconnect_info.as_ref().and_then(|r| r.layout.clone()),
- is_welcome_screen,
);
os_input.connect_to_server(&zellij_ipc_pipe);
@@ -124,9 +120,6 @@ pub fn zellij_server_listener(
reconnect_to_session = Some(connect_to_session);
continue 'reconnect_loop;
},
- Some((ServerToClientMsg::WriteConfigToDisk { config }, _)) => {
- handle_config_write(&os_input, config);
- },
Some((ServerToClientMsg::QueryTerminalSize, _)) => {
client_connection_bus.send_control(
WebServerToWebClientControlMessage::QueryTerminalSize,
@@ -149,6 +142,53 @@ pub fn zellij_server_listener(
},
);
},
+ Some((ServerToClientMsg::ConfigFileUpdated, _)) => {
+
+ if let Some(config_file_path) = &config_file_path {
+ if let Ok(new_config) = Config::from_path(&config_file_path, Some(config.clone())) {
+ let set_config_payload = SetConfigPayload::from(&new_config);
+
+ let client_ids: Vec<String> = {
+ let connection_table_lock = connection_table.lock().unwrap();
+ connection_table_lock
+ .client_id_to_channels
+ .keys()
+ .cloned()
+ .collect()
+ };
+
+ let config_message =
+ WebServerToWebClientControlMessage::SetConfig(set_config_payload);
+ let config_msg_json = match serde_json::to_string(&config_message) {
+ Ok(json) => json,
+ Err(e) => {
+ log::error!("Failed to serialize config message: {}", e);
+ continue;
+ },
+ };
+
+ for client_id in client_ids {
+ if let Some(control_tx) = connection_table
+ .lock()
+ .unwrap()
+ .get_client_control_tx(&client_id)
+ {
+ let ws_message = config_msg_json.clone();
+ match control_tx.send(ws_message.into()) {
+ Ok(_) => {}, // no-op
+ Err(e) => {
+ log::error!(
+ "Failed to send config update to client {}: {}",
+ client_id,
+ e
+ );
+ },
+ }
+ }
+ }
+ }
+ }
+ },
_ => {
// server disconnected, stop trying to listen otherwise we retry
// indefinitely and get 100% CPU
@@ -189,22 +229,6 @@ fn handle_exit_reason(client_connection_bus: &mut ClientConnectionBus, exit_reas
client_connection_bus.close_connection();
}
-fn handle_config_write(os_input: &Box<dyn ClientOsApi>, config: String) {
- match Config::write_config_to_disk(config, &CliArgs::default()) {
- 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));
- },
- }
-}
-
fn reload_config_from_disk(
config_without_layout: &mut Config,
config_options_without_layout: &mut Options,
diff --git a/zellij-client/src/web_client/session_management.rs b/zellij-client/src/web_client/session_management.rs
index 6e765597..d0dc9c13 100644
--- a/zellij-client/src/web_client/session_management.rs
+++ b/zellij-client/src/web_client/session_management.rs
@@ -3,24 +3,18 @@ use crate::spawn_server;
use std::{fs, path::PathBuf};
use zellij_utils::{
- cli::CliArgs,
+ consts::session_layout_cache_file_name,
data::{ConnectToSession, LayoutInfo, WebSharing},
envs,
- input::{
- config::{Config, ConfigError},
- layout::Layout,
- options::Options,
- },
+ input::{cli_assets::CliAssets, config::Config, options::Options},
ipc::{ClientAttributes, ClientToServerMsg},
sessions::{generate_unique_session_name, resurrection_layout, session_exists},
- setup::{find_default_config_dir, get_layout_dir},
};
pub fn build_initial_connection(
session_name: Option<String>,
config: &Config,
-) -> Result<(Option<ConnectToSession>, bool), &'static str> {
- // bool -> is_welcome_screen
+) -> Result<Option<ConnectToSession>, &'static str> {
let should_start_with_welcome_screen = session_name.is_none();
let default_layout_from_config =
LayoutInfo::from_config(&config.options.layout_dir, &config.options.default_layout);
@@ -29,123 +23,69 @@ pub fn build_initial_connection(
else {
return Err("Failed to generate unique session name, bailing.");
};
- Ok((
- Some(ConnectToSession {
- name: Some(initial_session_name.clone()),
- layout: Some(LayoutInfo::BuiltIn("welcome".to_owned())),
- ..Default::default()
- }),
- should_start_with_welcome_screen,
- ))
+ Ok(Some(ConnectToSession {
+ name: Some(initial_session_name.clone()),
+ layout: Some(LayoutInfo::BuiltIn("welcome".to_owned())),
+ ..Default::default()
+ }))
} else if let Some(session_name) = session_name {
- Ok((
- Some(ConnectToSession {
- name: Some(session_name.clone()),
- layout: default_layout_from_config,
- ..Default::default()
- }),
- should_start_with_welcome_screen,
- ))
+ Ok(Some(ConnectToSession {
+ name: Some(session_name.clone()),
+ layout: default_layout_from_config,
+ ..Default::default()
+ }))
} else if default_layout_from_config.is_some() {
- Ok((
- Some(ConnectToSession {
- layout: default_layout_from_config,
- ..Default::default()
- }),
- should_start_with_welcome_screen,
- ))
+ Ok(Some(ConnectToSession {
+ layout: default_layout_from_config,
+ ..Default::default()
+ }))
} else {
- Ok((None, should_start_with_welcome_screen))
- }
-}
-
-fn layout_for_new_session(
- config: &Config,
- requested_layout: Option<LayoutInfo>,
-) -> Result<(Layout, Config), ConfigError> {
- let layout_dir = config
- .options
- .layout_dir
- .clone()
- .or_else(|| get_layout_dir(find_default_config_dir()));
- match requested_layout {
- Some(LayoutInfo::BuiltIn(layout_name)) => Layout::from_default_assets(
- &PathBuf::from(layout_name),
- layout_dir.clone(),
- config.clone(),
- ),
- Some(LayoutInfo::File(layout_name)) => Layout::from_path_or_default(
- Some(&PathBuf::from(layout_name)),
- layout_dir.clone(),
- config.clone(),
- ),
- Some(LayoutInfo::Url(url)) => Layout::from_url(&url, config.clone()),
- Some(LayoutInfo::Stringified(stringified_layout)) => {
- Layout::from_stringified_layout(&stringified_layout, config.clone())
- },
- None => Layout::from_default_assets(
- &PathBuf::from("default"),
- layout_dir.clone(),
- config.clone(),
- ),
+ Ok(None)
}
}
pub fn spawn_session_if_needed(
session_name: &str,
- path: String,
client_attributes: ClientAttributes,
- config: &Config,
+ config_file_path: Option<PathBuf>,
config_options: &Options,
- is_web_client: bool,
os_input: Box<dyn ClientOsApi>,
requested_layout: Option<LayoutInfo>,
- is_welcome_screen: bool,
) -> (ClientToServerMsg, PathBuf) {
if session_exists(&session_name).unwrap_or(false) {
ipc_pipe_and_first_message_for_existing_session(
- path,
+ session_name,
client_attributes,
- &config,
- &config_options,
- is_web_client,
+ config_file_path,
+ config_options.clone(),
)
} else {
- let force_run_commands = false;
- let resurrection_layout =
- resurrection_layout(&session_name)
- .ok()
- .flatten()
- .map(|mut resurrection_layout| {
- if force_run_commands {
- resurrection_layout.recursively_add_start_suspended(Some(false));
- }
- resurrection_layout
- });
+ // we still parse the resurrection layout here even though we don't use it just in case
+ // it's corrupted
+ let resurrection_layout = resurrection_layout(&session_name).ok().flatten();
- match resurrection_layout {
- Some(resurrection_layout) => spawn_new_session(
+ if resurrection_layout.is_some() {
+ spawn_new_session(
&session_name,
os_input.clone(),
- config.clone(),
+ config_file_path,
config_options.clone(),
- Some(resurrection_layout),
+ Some(LayoutInfo::File(
+ session_layout_cache_file_name(&session_name)
+ .display()
+ .to_string(),
+ )),
client_attributes,
- is_welcome_screen,
- ),
- None => {
- let new_session_layout = layout_for_new_session(&config, requested_layout);
-
- spawn_new_session(
- &session_name,
- os_input.clone(),
- config.clone(),
- config_options.clone(),
- new_session_layout.ok().map(|(l, _c)| l),
- client_attributes,
- is_welcome_screen,
- )
- },
+ )
+ } else {
+ spawn_new_session(
+ &session_name,
+ os_input.clone(),
+ config_file_path,
+ config_options.clone(),
+ requested_layout,
+ client_attributes,
+ )
}
}
}
@@ -153,11 +93,10 @@ pub fn spawn_session_if_needed(
fn spawn_new_session(
name: &str,
mut os_input: Box<dyn ClientOsApi>,
- mut config: Config,
- config_opts: Options,
- layout: Option<Layout>,
+ config_file_path: Option<PathBuf>,
+ mut config_opts: Options,
+ layout_info: Option<LayoutInfo>,
client_attributes: ClientAttributes,
- is_welcome_screen: bool,
) -> (ClientToServerMsg, PathBuf) {
let debug = false;
envs::set_session_name(name.to_owned());
@@ -173,38 +112,34 @@ fn spawn_new_session(
spawn_server(&*zellij_ipc_pipe, debug).unwrap();
- let successfully_written_config = Config::write_config_to_disk_if_it_does_not_exist(
- config.to_string(true),
- &Default::default(),
- );
- let should_launch_setup_wizard = successfully_written_config;
- let cli_args = CliArgs::default();
- config.options.web_server = Some(true);
- config.options.web_sharing = Some(WebSharing::On);
+ config_opts.web_server = Some(true);
+ config_opts.web_sharing = Some(WebSharing::On);
+ let cli_assets = CliAssets {
+ config_file_path,
+ config_dir: None,
+ should_ignore_config: false,
+ configuration_options: Some(config_opts),
+ layout: layout_info,
+ terminal_window_size: client_attributes.size,
+ data_dir: None,
+ is_debug: false,
+ max_panes: None,
+ force_run_layout_commands: false,
+ cwd: None,
+ };
let is_web_client = true;
(
- ClientToServerMsg::NewClient(
- client_attributes,
- Box::new(cli_args),
- Box::new(config.clone()),
- Box::new(config_opts.clone()),
- Box::new(layout.unwrap()),
- Box::new(config.plugins.clone()),
- should_launch_setup_wizard,
- is_web_client,
- is_welcome_screen,
- ),
+ ClientToServerMsg::FirstClientConnected(cli_assets, is_web_client),
zellij_ipc_pipe,
)
}
fn ipc_pipe_and_first_message_for_existing_session(
- session_name: String,
+ session_name: &str,
client_attributes: ClientAttributes,
- config: &Config,
- config_options: &Options,
- is_web_client: bool,
+ config_file_path: Option<PathBuf>,
+ mut config_opts: Options,
) -> (ClientToServerMsg, PathBuf) {
let zellij_ipc_pipe: PathBuf = {
let mut sock_dir = zellij_utils::consts::ZELLIJ_SOCK_DIR.clone();
@@ -213,13 +148,24 @@ fn ipc_pipe_and_first_message_for_existing_session(
sock_dir.push(session_name);
sock_dir
};
- let first_message = ClientToServerMsg::AttachClient(
- client_attributes,
- config.clone(),
- config_options.clone(),
- None,
- None,
- is_web_client,
- );
+
+ config_opts.web_server = Some(true);
+ config_opts.web_sharing = Some(WebSharing::On);
+ let cli_assets = CliAssets {
+ config_file_path,
+ config_dir: None,
+ should_ignore_config: false,
+ configuration_options: Some(config_opts),
+ layout: None,
+ terminal_window_size: client_attributes.size,
+ data_dir: None,
+ is_debug: false,
+ max_panes: None,
+ force_run_layout_commands: false,
+ cwd: None,
+ };
+ let is_web_client = true;
+
+ let first_message = ClientToServerMsg::AttachClient(cli_assets, None, None, is_web_client);
(first_message, zellij_ipc_pipe)
}
diff --git a/zellij-client/src/web_client/types.rs b/zellij-client/src/web_client/types.rs
index c751199b..201a0cf4 100644
--- a/zellij-client/src/web_client/types.rs
+++ b/zellij-client/src/web_client/types.rs
@@ -37,14 +37,11 @@ pub trait SessionManager: Send + Sync + std::fmt::Debug {
fn spawn_session_if_needed(
&self,
session_name: &str,
- path: String,
client_attributes: ClientAttributes,
- config: &Config,
+ config_file_path: Option<PathBuf>,
config_options: &Options,
- is_web_client: bool,
os_input: Box<dyn ClientOsApi>,
requested_layout: Option<LayoutInfo>,
- is_welcome_screen: bool,
) -> (ClientToServerMsg, PathBuf);
}
@@ -69,25 +66,19 @@ impl SessionManager for RealSessionManager {
fn spawn_session_if_needed(
&self,
session_name: &str,
- path: String,
client_attributes: ClientAttributes,
- config: &Config,
+ config_file_path: Option<PathBuf>,
config_options: &Options,
- is_web_client: bool,
os_input: Box<dyn ClientOsApi>,
requested_layout: Option<LayoutInfo>,
- is_welcome_screen: bool,
) -> (ClientToServerMsg, PathBuf) {
crate::web_client::session_management::spawn_session_if_needed(
session_name,
- path,
client_attributes,
- config,
+ config_file_path,
config_options,
- is_web_client,
os_input,
requested_layout,
- is_welcome_screen,
)
}
}
diff --git a/zellij-client/src/web_client/unit/web_client_tests.rs b/zellij-client/src/web_client/unit/web_client_tests.rs
index c6967886..e0fa0516 100644
--- a/zellij-client/src/web_client/unit/web_client_tests.rs
+++ b/zellij-client/src/web_client/unit/web_client_tests.rs
@@ -8,6 +8,7 @@ use std::sync::{Arc, Mutex};
use tokio::time::timeout;
use tokio_tungstenite::tungstenite::http::Request;
use tokio_tungstenite::{connect_async, tungstenite::Message};
+use zellij_utils::input::cli_assets::CliAssets;
use zellij_utils::input::layout::Layout;
use zellij_utils::{consts::VERSION, input::config::Config, input::options::Options};
@@ -1290,25 +1291,28 @@ impl SessionManager for MockSessionManager {
fn spawn_session_if_needed(
&self,
session_name: &str,
- _path: String,
client_attributes: ClientAttributes,
- config: &Config,
+ config_file_path: Option<PathBuf>,
config_options: &Options,
- is_web_client: bool,
_os_input: Box<dyn ClientOsApi>,
_requested_layout: Option<LayoutInfo>,
- _is_welcome_screen: bool,
) -> (ClientToServerMsg, PathBuf) {
let mock_ipc_path = PathBuf::from(format!("/tmp/mock_zellij_{}", session_name));
- let first_message = ClientToServerMsg::AttachClient(
- client_attributes,
- config.clone(),
- config_options.clone(),
- None,
- None,
- is_web_client,
- );
+ let cli_assets = CliAssets {
+ config_file_path,
+ config_dir: None,
+ should_ignore_config: false,
+ configuration_options: Some(config_options.clone()),
+ layout: None,
+ terminal_window_size: client_attributes.size,
+ data_dir: None,
+ is_debug: false,
+ max_panes: None,
+ force_run_layout_commands: false,
+ cwd: None,
+ };
+ let first_message = ClientToServerMsg::AttachClient(cli_assets, None, None, true);
(first_message, mock_ipc_path)
}
diff --git a/zellij-server/Cargo.toml b/zellij-server/Cargo.toml
index 9085d371..a497ca5d 100644
--- a/zellij-server/Cargo.toml
+++ b/zellij-server/Cargo.toml
@@ -47,6 +47,7 @@ uuid = { workspace = true }
vte = { workspace = true }
wasmtime-wasi = { version = "29.0.1", default-features = false, features = ["preview1"] } # Keep in sync with wasmtime
zellij-utils = { workspace = true }
+tokio = { workspace = true }
[dependencies.wasmtime]
version = "29.0.1" # Keep in sync with wasmtime-wasi
diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs
index f5004eb8..87536b43 100644
--- a/zellij-server/src/lib.rs
+++ b/zellij-server/src/lib.rs
@@ -32,6 +32,8 @@ use std::{
use zellij_utils::envs;
use zellij_utils::pane_size::Size;
+use zellij_utils::input::cli_assets::CliAssets;
+
use wasmtime::{Config as WasmtimeConfig, Engine, Strategy};
use crate::{
@@ -44,17 +46,19 @@ use crate::{
use route::route_thread_main;
use zellij_utils::{
channels::{self, ChannelWithContext, SenderWithContext},
- cli::CliArgs,
consts::{
DEFAULT_SCROLL_BUFFER_SIZE, SCROLL_BUFFER_SIZE, ZELLIJ_SEEN_RELEASE_NOTES_CACHE_FILE,
},
- data::{ConnectToSession, Event, InputMode, KeyWithModifier, PluginCapabilities, WebSharing},
+ data::{
+ ConnectToSession, Event, InputMode, KeyWithModifier, LayoutInfo, PluginCapabilities, Style,
+ WebSharing,
+ },
errors::{prelude::*, ContextType, ErrorInstruction, FatalError, ServerContext},
home::{default_layout_dir, get_default_data_dir},
input::{
actions::Action,
command::{RunCommand, TerminalAction},
- config::Config,
+ config::{watch_config_file_changes, Config},
get_mode_info,
keybinds::Keybinds,
layout::{FloatingPaneLayout, Layout, PluginAlias, Run, RunPluginOrAlias},
@@ -70,16 +74,9 @@ pub type ClientId = u16;
/// Instructions related to server-side application
#[derive(Debug, Clone)]
pub enum ServerInstruction {
- NewClient(
- ClientAttributes,
- Box<CliArgs>,
- Box<Config>, // represents the saved config
- Box<Options>, // represents the runtime configuration options
- Box<Layout>,
- Box<PluginAliases>,
- bool, // should launch setup wizard
+ FirstClientConnected(
+ CliAssets,
bool, // is_web_client
- bool, // layout_is_welcome_screen
ClientId,
),
Render(Option<HashMap<ClientId, String>>),
@@ -90,9 +87,7 @@ pub enum ServerInstruction {
KillSession,
DetachSession(Vec<ClientId>),
AttachClient(
- ClientAttributes,
- Config, // represents the saved config
- Options, // represents the runtime configuration options
+ CliAssets,
Option<usize>, // tab position to focus
Option<(u32, bool)>, // (pane_id, is_plugin) => pane_id to focus
bool, // is_web_client
@@ -116,7 +111,7 @@ pub enum ServerInstruction {
config: String,
write_config_to_disk: bool,
},
- ConfigWrittenToDisk(ClientId, Config),
+ ConfigWrittenToDisk(Config),
FailedToWriteConfigToDisk(ClientId, Option<PathBuf>), // Pathbuf - file we failed to write
RebindKeys {
client_id: ClientId,
@@ -135,7 +130,7 @@ pub enum ServerInstruction {
impl From<&ServerInstruction> for ServerContext {
fn from(server_instruction: &ServerInstruction) -> Self {
match *server_instruction {
- ServerInstruction::NewClient(..) => ServerContext::NewClient,
+ ServerInstruction::FirstClientConnected(..) => ServerContext::NewClient,
ServerInstruction::Render(..) => ServerContext::Render,
ServerInstruction::UnblockInputThread => ServerContext::UnblockInputThread,
ServerInstruction::ClientExit(..) => ServerContext::ClientExit,
@@ -161,7 +156,6 @@ impl From<&ServerInstruction> for ServerContext {
ServerContext::ChangeModeForAllClients
},
ServerInstruction::Reconfigure { .. } => ServerContext::Reconfigure,
- ServerInstruction::ConfigWrittenToDisk(..) => ServerContext::ConfigWrittenToDisk,
ServerInstruction::FailedToWriteConfigToDisk(..) => {
ServerContext::FailedToWriteConfigToDisk
},
@@ -173,6 +167,7 @@ impl From<&ServerInstruction> for ServerContext {
},
ServerInstruction::WebServerStarted(..) => ServerContext::WebServerStarted,
ServerInstruction::FailedToStartWebServer(..) => ServerContext::FailedToStartWebServer,
+ ServerInstruction::ConfigWrittenToDisk(..) => ServerContext::ConfigWrittenToDisk,
ServerInstruction::SendWebClientsForbidden(..) => {
ServerContext::SendWebClientsForbidden
},
@@ -189,20 +184,15 @@ 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>, // the config as it is on disk (not guaranteed),
+ saved_config: Config, // the config as it is on disk (not guaranteed),
// when changed, this resets the runtime config to
// be identical to it and override any previous
// changes
}
impl SessionConfiguration {
- pub fn new_saved_config(
- &mut self,
- client_id: ClientId,
- new_saved_config: Config,
- ) -> Vec<(ClientId, Config)> {
- self.saved_config
- .insert(client_id, new_saved_config.clone());
+ pub fn change_saved_config(&mut self, new_saved_config: Config) -> Vec<(ClientId, Config)> {
+ self.saved_config = new_saved_config.clone();
let mut config_changes = vec![];
for (client_id, current_runtime_config) in self.runtime_config.iter_mut() {
@@ -213,8 +203,8 @@ impl SessionConfiguration {
}
config_changes
}
- 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_saved_configuration(&mut self, config: Config) {
+ self.saved_config = config;
}
pub fn set_client_runtime_configuration(&mut self, client_id: ClientId, client_config: Config) {
self.runtime_config.insert(client_id, client_config);
@@ -222,21 +212,21 @@ impl SessionConfiguration {
pub fn get_client_keybinds(&self, client_id: &ClientId) -> Keybinds {
self.runtime_config
.get(client_id)
- .or_else(|| self.saved_config.get(client_id))
+ .or_else(|| Some(&self.saved_config))
.map(|c| c.keybinds.clone())
.unwrap_or_default()
}
pub fn get_client_default_input_mode(&self, client_id: &ClientId) -> InputMode {
self.runtime_config
.get(client_id)
- .or_else(|| self.saved_config.get(client_id))
+ .or_else(|| Some(&self.saved_config))
.and_then(|c| c.options.default_mode.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))
+ .or_else(|| Some(&self.saved_config))
.cloned()
.unwrap_or_default()
}
@@ -274,9 +264,8 @@ impl SessionConfiguration {
let mut config_changed = false;
if self.runtime_config.get(client_id).is_none() {
- if let Some(saved_config) = self.saved_config.get(client_id) {
- self.runtime_config.insert(*client_id, saved_config.clone());
- }
+ self.runtime_config
+ .insert(*client_id, self.saved_config.clone());
}
match self.runtime_config.get_mut(client_id) {
Some(config) => {
@@ -334,6 +323,7 @@ pub(crate) struct SessionMetaData {
plugin_thread: Option<thread::JoinHandle<()>>,
pty_writer_thread: Option<thread::JoinHandle<()>>,
background_jobs_thread: Option<thread::JoinHandle<()>>,
+ config_file_path: Option<PathBuf>,
}
impl SessionMetaData {
@@ -659,40 +649,61 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
let (instruction, mut err_ctx) = server_receiver.recv().unwrap();
err_ctx.add_call(ContextType::IPCServer((&instruction).into()));
match instruction {
- ServerInstruction::NewClient(
- // TODO: rename to FirstClientConnected?
- client_attributes,
- opts,
- config,
- runtime_config_options,
- layout,
- plugin_aliases,
- should_launch_setup_wizard,
- is_web_client,
- layout_is_welcome_screen,
- client_id,
- ) => {
+ ServerInstruction::FirstClientConnected(cli_assets, is_web_client, client_id) => {
+ let (config, layout) = cli_assets.load_config_and_layout();
+ let layout_is_welcome_screen = cli_assets.layout
+ == Some(LayoutInfo::BuiltIn("welcome".to_owned()))
+ || config.options.default_layout == Some(PathBuf::from("welcome"));
+
+ let successfully_written_config = Config::write_config_to_disk_if_it_does_not_exist(
+ config.to_string(true),
+ &cli_assets.config_file_path,
+ );
+ // 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;
+
+ let runtime_config_options = match &cli_assets.configuration_options {
+ Some(configuration_options) => {
+ config.options.merge(configuration_options.clone())
+ },
+ None => config.options.clone(),
+ };
+
+ let client_attributes = ClientAttributes {
+ size: cli_assets.terminal_window_size,
+ style: Style {
+ colors: config
+ .theme_config(runtime_config_options.theme.as_ref())
+ .unwrap_or_else(|| default_palette().into()),
+ rounded_corners: config.ui.pane_frames.rounded_corners,
+ hide_session_name: config.ui.pane_frames.hide_session_name,
+ },
+ };
+
let mut session = init_session(
os_input.clone(),
to_server.clone(),
client_attributes.clone(),
- SessionOptions {
- opts,
- layout: layout.clone(),
- config_options: runtime_config_options.clone(),
- },
- *config.clone(),
- plugin_aliases,
+ Box::new(runtime_config_options.clone()), // TODO: no box
+ Box::new(layout.clone()), // TODO: no box
+ cli_assets.clone(),
+ config.clone(),
+ config.plugins.clone(),
client_id,
);
let mut runtime_configuration = config.clone();
- runtime_configuration.options = *runtime_config_options.clone();
+ runtime_configuration.options = runtime_config_options.clone();
session
.session_configuration
- .set_client_saved_configuration(client_id, *config.clone());
+ .set_saved_configuration(config.clone());
session
.session_configuration
- .set_client_runtime_configuration(client_id, *runtime_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
@@ -713,7 +724,9 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
..Default::default()
})
});
- let cwd = runtime_config_options.default_cwd;
+ let cwd = cli_assets
+ .cwd
+ .or_else(|| runtime_config_options.default_cwd);
let spawn_tabs = |tab_layout,
floating_panes_layout,
@@ -798,9 +811,7 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.unwrap();
},
ServerInstruction::AttachClient(
- attrs,
- config,
- runtime_config_options,
+ cli_assets,
tab_position_to_focus,
pane_id_to_focus,
is_web_client,
@@ -808,14 +819,27 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
) => {
let mut rlock = session_data.write().unwrap();
let session_data = rlock.as_mut().unwrap();
+ let config = session_data.session_configuration.saved_config.clone();
+ let runtime_config_options = match cli_assets.configuration_options {
+ Some(configuration_options) => config.options.merge(configuration_options),
+ None => config.options.clone(),
+ };
+
+ let client_attributes = ClientAttributes {
+ size: cli_assets.terminal_window_size,
+ style: Style {
+ colors: config
+ .theme_config(runtime_config_options.theme.as_ref())
+ .unwrap_or_else(|| default_palette().into()),
+ rounded_corners: config.ui.pane_frames.rounded_corners,
+ hide_session_name: config.ui.pane_frames.hide_session_name,
+ },
+ };
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();
@@ -825,7 +849,7 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
session_state.write().unwrap().set_client_data(
client_id,
- attrs.size,
+ client_attributes.size,
is_web_client,
);
let min_size = session_state
@@ -853,7 +877,7 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
let default_mode = config.options.default_mode.unwrap_or_default();
let mode_info = get_mode_info(
default_mode,
- &attrs,
+ &client_attributes,
session_data.capabilities,
&session_data
.session_configuration
@@ -1229,42 +1253,22 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.unwrap()
.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
- );
- }
-
- if runtime_config_changed {
- let config_was_written_to_disk = false;
- session_data
- .write()
- .unwrap()
- .as_mut()
- .unwrap()
- .propagate_configuration_changes(
- vec![(client_id, new_config)],
- config_was_written_to_disk,
- );
- }
- }
+ update_new_saved_config(
+ new_config,
+ write_config_to_disk,
+ runtime_config_changed,
+ &session_data,
+ client_id,
+ );
},
- ServerInstruction::ConfigWrittenToDisk(client_id, new_config) => {
+ ServerInstruction::ConfigWrittenToDisk(new_config) => {
let changes = session_data
.write()
.unwrap()
.as_mut()
.unwrap()
.session_configuration
- .new_saved_config(client_id, new_config);
+ .change_saved_config(new_config);
let config_was_written_to_disk = true;
session_data
.write()
@@ -1272,6 +1276,15 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.as_mut()
.unwrap()
.propagate_configuration_changes(changes, config_was_written_to_disk);
+ let client_ids = session_state.read().unwrap().client_ids();
+ for client_id in client_ids {
+ send_to_client!(
+ client_id,
+ os_input,
+ ServerToClientMsg::ConfigFileUpdated,
+ session_state
+ );
+ }
},
ServerInstruction::FailedToWriteConfigToDisk(_client_id, file_path) => {
session_data
@@ -1296,32 +1309,14 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.unwrap()
.session_configuration
.rebind_keys(&client_id, keys_to_rebind, keys_to_unbind);
- 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
- );
- }
- if runtime_config_changed {
- let config_was_written_to_disk = false;
- session_data
- .write()
- .unwrap()
- .as_mut()
- .unwrap()
- .propagate_configuration_changes(
- vec![(client_id, new_config)],
- config_was_written_to_disk,
- );
- }
- }
+ update_new_saved_config(
+ new_config,
+ write_config_to_disk,
+ runtime_config_changed,
+ &session_data,
+ client_id,
+ );
},
ServerInstruction::StartWebServer(client_id) => {
if cfg!(feature = "web_server_capability") {
@@ -1424,26 +1419,17 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
drop(std::fs::remove_file(&socket_path));
}
-pub struct SessionOptions {
- pub opts: Box<CliArgs>,
- pub config_options: Box<Options>,
- pub layout: Box<Layout>,
-}
-
fn init_session(
os_input: Box<dyn ServerOsApi>,
to_server: SenderWithContext<ServerInstruction>,
client_attributes: ClientAttributes,
- options: SessionOptions,
+ config_options: Box<Options>,
+ layout: Box<Layout>,
+ cli_assets: CliAssets,
mut config: Config,
- plugin_aliases: Box<PluginAliases>,
+ plugin_aliases: PluginAliases,
client_id: ClientId,
) -> SessionMetaData {
- let SessionOptions {
- opts,
- config_options,
- layout,
- } = options;
config.options = config.options.merge(*config_options.clone());
let _ = SCROLL_BUFFER_SIZE.set(
@@ -1473,7 +1459,7 @@ fn init_session(
let to_background_jobs = SenderWithContext::new(to_background_jobs);
// Determine and initialize the data directory
- let data_dir = opts.data_dir.unwrap_or_else(get_default_data_dir);
+ let data_dir = cli_assets.data_dir.unwrap_or_else(get_default_data_dir);
let capabilities = PluginCapabilities {
arrow_fonts: config_options.simplified_ui.unwrap_or_default(),
@@ -1519,7 +1505,7 @@ fn init_session(
Some(&to_background_jobs),
Some(os_input.clone()),
),
- opts.debug,
+ cli_assets.is_debug,
config_options.scrollback_editor.clone(),
config_options.post_command_discovery_hook.clone(),
);
@@ -1542,10 +1528,10 @@ fn init_session(
Some(&to_background_jobs),
Some(os_input.clone()),
);
- let max_panes = opts.max_panes;
+ let max_panes = cli_assets.max_panes;
let client_attributes_clone = client_attributes.clone();
- let debug = opts.debug;
+ let debug = cli_assets.is_debug;
let layout = layout.clone();
let config = config.clone();
move || {
@@ -1654,6 +1640,9 @@ fn init_session(
}
})
.unwrap();
+ if let Some(config_file_path) = cli_assets.config_file_path.clone() {
+ report_changes_in_config_file(config_file_path, to_server.clone());
+ }
SessionMetaData {
senders: ThreadSenders {
@@ -1680,6 +1669,7 @@ fn init_session(
web_sharing: config.options.web_sharing.unwrap_or(WebSharing::Off),
#[cfg(not(feature = "web_server_capability"))]
web_sharing: WebSharing::Disabled,
+ config_file_path: cli_assets.config_file_path,
}
}
@@ -1756,6 +1746,110 @@ fn should_show_startup_tip(
}
}
+fn report_changes_in_config_file(
+ config_file_path: PathBuf,
+ to_server: SenderWithContext<ServerInstruction>,
+) {
+ std::thread::spawn(move || {
+ let rt = tokio::runtime::Runtime::new().unwrap();
+ rt.block_on(async move {
+ let to_server = to_server.clone();
+ watch_config_file_changes(config_file_path, move |new_config| {
+ let to_server = to_server.clone();
+ async move {
+ let _ = to_server.send(ServerInstruction::ConfigWrittenToDisk(new_config));
+ }
+ })
+ .await;
+ });
+ });
+}
+
+fn update_new_saved_config(
+ new_config: Option<Config>,
+ write_config_to_disk: bool,
+ runtime_config_changed: bool,
+ session_data: &Arc<RwLock<Option<SessionMetaData>>>,
+ client_id: ClientId,
+) {
+ if let Some(new_config) = new_config {
+ if write_config_to_disk {
+ let clear_defaults = true;
+ let config_file_path = session_data
+ .read()
+ .unwrap()
+ .as_ref()
+ .unwrap()
+ .config_file_path
+ .clone();
+
+ let Some(config_file_path) = config_file_path.as_ref() else {
+ log::error!("No config file path found.");
+ session_data
+ .write()
+ .unwrap()
+ .as_ref()
+ .unwrap()
+ .senders
+ .send_to_plugin(PluginInstruction::FailedToWriteConfigToDisk {
+ file_path: None,
+ })
+ .unwrap();
+ return;
+ };
+ match Config::write_config_to_disk(
+ new_config.to_string(clear_defaults),
+ &config_file_path,
+ ) {
+ Ok(written_config) => {
+ let changes = session_data
+ .write()
+ .unwrap()
+ .as_mut()
+ .unwrap()
+ .session_configuration
+ .change_saved_config(written_config);
+ let config_was_written_to_disk = true;
+ session_data
+ .write()
+ .unwrap()
+ .as_mut()
+ .unwrap()
+ .propagate_configuration_changes(changes, config_was_written_to_disk);
+ },
+ 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);
+ session_data
+ .write()
+ .unwrap()
+ .as_ref()
+ .unwrap()
+ .senders
+ .send_to_plugin(PluginInstruction::FailedToWriteConfigToDisk {
+ file_path: e,
+ })
+ .unwrap();
+ },
+ }
+ } else if runtime_config_changed {
+ let config_was_written_to_disk = false;
+ session_data
+ .write()
+ .unwrap()
+ .as_mut()
+ .unwrap()
+ .propagate_configuration_changes(
+ vec![(client_id, new_config)],
+ config_was_written_to_disk,
+ );
+ }
+ }
+}
+
#[cfg(not(feature = "singlepass"))]
fn get_engine() -> Engine {
log::info!("Compiling plugins using Cranelift");
diff --git a/zellij-server/src/plugins/mod.rs b/zellij-server/src/plugins/mod.rs
index 1c77df5e..6149ac97 100644
--- a/zellij-server/src/plugins/mod.rs
+++ b/zellij-server/src/plugins/mod.rs
@@ -230,7 +230,7 @@ pub(crate) fn plugin_thread_main(
capabilities: PluginCapabilities,
client_attributes: ClientAttributes,
default_shell: Option<TerminalAction>,
- plugin_aliases: Box<PluginAliases>,
+ plugin_aliases: PluginAliases,
default_mode: InputMode,
default_keybinds: Keybinds,
background_plugins: HashSet<RunPluginOrAlias>,
diff --git a/zellij-server/src/plugins/unit/plugin_tests.rs b/zellij-server/src/plugins/unit/plugin_tests.rs
index 700011c8..da725443 100644
--- a/zellij-server/src/plugins/unit/plugin_tests.rs
+++ b/zellij-server/src/plugins/unit/plugin_tests.rs
@@ -365,7 +365,7 @@ fn create_plugin_thread(
plugin_capabilities,
client_attributes,
default_shell_action,
- Box::new(plugin_aliases),
+ plugin_aliases,
InputMode::Normal,
Keybinds::default(),
Default::default(),
@@ -449,7 +449,7 @@ fn create_plugin_thread_with_server_receiver(
plugin_capabilities,
client_attributes,
default_shell_action,
- Box::new(PluginAliases::default()),
+ PluginAliases::default(),
InputMode::Normal,
Keybinds::default(),
Default::default(),
@@ -539,7 +539,7 @@ fn create_plugin_thread_with_pty_receiver(
plugin_capabilities,
client_attributes,
default_shell_action,
- Box::new(PluginAliases::default()),
+ PluginAliases::default(),
InputMode::Normal,
Keybinds::default(),
Default::default(),
@@ -624,7 +624,7 @@ fn create_plugin_thread_with_background_jobs_receiver(
plugin_capabilities,
client_attributes,
default_shell_action,
- Box::new(PluginAliases::default()),
+ PluginAliases::default(),
InputMode::Normal,
Keybinds::default(),
Default::default(),
diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs
index 915dbfa1..65f4d1dd 100644
--- a/zellij-server/src/route.rs
+++ b/zellij-server/src/route.rs
@@ -1104,27 +1104,10 @@ pub(crate) fn route_thread_main(
)
.with_context(err_context)?;
},
- ClientToServerMsg::NewClient(
- client_attributes,
- cli_args,
- config,
- runtime_config_options,
- layout,
- plugin_aliases,
- should_launch_setup_wizard,
- is_web_client,
- layout_is_welcome_screen,
- ) => {
- let new_client_instruction = ServerInstruction::NewClient(
- client_attributes,
- cli_args,
- config,
- runtime_config_options,
- layout,
- plugin_aliases,
- should_launch_setup_wizard,
+ ClientToServerMsg::FirstClientConnected(cli_assets, is_web_client) => {
+ let new_client_instruction = ServerInstruction::FirstClientConnected(
+ cli_assets,
is_web_client,
- layout_is_welcome_screen,
client_id,
);
to_server
@@ -1132,9 +1115,7 @@ pub(crate) fn route_thread_main(
.with_context(err_context)?;
},
ClientToServerMsg::AttachClient(
- client_attributes,
- config,
- runtime_config_options,
+ cli_assets,
tab_position_to_focus,
pane_id_to_focus,
is_web_client,
@@ -1148,9 +1129,7 @@ pub(crate) fn route_thread_main(
let should_allow_connection = !is_web_client || allow_web_connections;
if should_allow_connection {
let attach_client_instruction = ServerInstruction::AttachClient(
- client_attributes,
- config,
- runtime_config_options,
+ cli_assets,
tab_position_to_focus,
pane_id_to_focus,
is_web_client,
@@ -1188,16 +1167,6 @@ pub(crate) fn route_thread_main(
let _ = to_server.send(ServerInstruction::DetachSession(client_id));
should_break = true;
},
- 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,
- ));
- },
ClientToServerMsg::WebServerStarted(base_url) => {
let _ = to_server.send(ServerInstruction::WebServerStarted(base_url));
},
diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs
index a1164864..845cf0e2 100644
--- a/zellij-server/src/unit/screen_tests.rs
+++ b/zellij-server/src/unit/screen_tests.rs
@@ -562,6 +562,7 @@ impl MockScreen {
layout,
current_input_modes: self.session_metadata.current_input_modes.clone(),
web_sharing: WebSharing::Off,
+ config_file_path: self.session_metadata.config_file_path.clone(),
}
}
}
@@ -621,6 +622,7 @@ impl MockScreen {
session_configuration: Default::default(),
current_input_modes: HashMap::new(),
web_sharing: WebSharing::Off,
+ config_file_path: None,
};
let os_input = FakeInputOutput::default();
diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs
index 55e2a76e..1fcba3b4 100644
--- a/zellij-utils/src/cli.rs
+++ b/zellij-utils/src/cli.rs
@@ -2,7 +2,7 @@ use crate::data::{Direction, InputMode, Resize};
use crate::setup::Setup;
use crate::{
consts::{ZELLIJ_CONFIG_DIR_ENV, ZELLIJ_CONFIG_FILE_ENV},
- input::{layout::PluginUserConfiguration, options::CliOptions},
+ input::{layout::PluginUserConfiguration, options::Options},
};
use clap::{Args, Parser, Subcommand};
use serde::{Deserialize, Serialize};
@@ -80,11 +80,28 @@ pub struct CliArgs {
pub debug: bool,
}
+impl CliArgs {
+ pub fn is_setup_clean(&self) -> bool {
+ if let Some(Command::Setup(ref setup)) = &self.command {
+ if setup.clean {
+ return true;
+ }
+ }
+ false
+ }
+ pub fn options(&self) -> Option<Options> {
+ if let Some(Command::Options(options)) = &self.command {
+ return Some(options.clone());
+ }
+ None
+ }
+}
+
#[derive(Debug, Subcommand, Clone, Serialize, Deserialize)]
pub enum Command {
/// Change the behaviour of zellij
#[clap(name = "options", value_parser)]
- Options(CliOptions),
+ Options(Options),
/// Setup zellij and check its configuration
#[clap(name = "setup", value_parser)]
@@ -191,7 +208,7 @@ impl WebCli {
pub enum SessionCommand {
/// Change the behaviour of zellij
#[clap(name = "options")]
- Options(CliOptions),
+ Options(Options),
}
#[derive(Debug, Subcommand, Clone, Serialize, Deserialize)]
diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs
index 8712f454..6cdac7f1 100644
--- a/zellij-utils/src/data.rs
+++ b/zellij-utils/src/data.rs
@@ -1675,11 +1675,11 @@ impl LayoutInfo {
}
pub fn from_config(
layout_dir: &Option<PathBuf>,
- default_layout: &Option<PathBuf>,
+ layout_path: &Option<PathBuf>,
) -> Option<Self> {
- match default_layout {
- Some(default_layout) => {
- if default_layout.extension().is_some() || default_layout.components().count() > 1 {
+ match layout_path {
+ Some(layout_path) => {
+ if layout_path.extension().is_some() || layout_path.components().count() > 1 {
let Some(layout_dir) = layout_dir
.as_ref()
.map(|l| l.clone())
@@ -1688,14 +1688,13 @@ impl LayoutInfo {
return None;
};
Some(LayoutInfo::File(
- layout_dir.join(default_layout).display().to_string(),
+ layout_dir.join(layout_path).display().to_string(),
))
- } else if default_layout.starts_with("http://")
- || default_layout.starts_with("https://")
+ } else if layout_path.starts_with("http://") || layout_path.starts_with("https://")
{
- Some(LayoutInfo::Url(default_layout.display().to_string()))
+ Some(LayoutInfo::Url(layout_path.display().to_string()))
} else {
- Some(LayoutInfo::BuiltIn(default_layout.display().to_string()))
+ Some(LayoutInfo::BuiltIn(layout_path.display().to_string()))
}
},
None => None,
diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs
index 10eed658..c93b5775 100644
--- a/zellij-utils/src/errors.rs
+++ b/zellij-utils/src/errors.rs
@@ -479,6 +479,7 @@ pub enum ClientContext {
WriteConfigToDisk,
StartWebServer,
RenamedSession,
+ ConfigFileUpdated,
}
/// Stack call representations corresponding to the different types of [`ServerInstruction`]s.
diff --git a/zellij-utils/src/input/cli_assets.rs b/zellij-utils/src/input/cli_assets.rs
new file mode 100644
index 00000000..0db1395b
--- /dev/null
+++ b/zellij-utils/src/input/cli_assets.rs
@@ -0,0 +1,86 @@
+use crate::data::LayoutInfo;
+use crate::input::options::Options;
+use crate::pane_size::Size;
+use crate::{
+ home::find_default_config_dir,
+ input::{config::Config, layout::Layout, theme::Themes},
+ setup::{get_default_themes, get_theme_dir},
+};
+use serde::{Deserialize, Serialize};
+use std::path::PathBuf;
+
+#[derive(Debug, Clone, Default, Serialize, Deserialize)]
+pub struct CliAssets {
+ pub config_file_path: Option<PathBuf>,
+ pub config_dir: Option<PathBuf>,
+ pub should_ignore_config: bool,
+ pub configuration_options: Option<Options>, // merged from everywhere: there are the source of truth
+ pub layout: Option<LayoutInfo>,
+ pub terminal_window_size: Size,
+ pub data_dir: Option<PathBuf>,
+ pub is_debug: bool,
+ pub max_panes: Option<usize>,
+ pub force_run_layout_commands: bool,
+ pub cwd: Option<PathBuf>,
+}
+
+impl CliAssets {
+ pub fn load_config_and_layout(&self) -> (Config, Layout) {
+ let config = {
+ if self.should_ignore_config {
+ Config::from_default_assets().unwrap_or_else(|_| Default::default())
+ } else if let Some(ref path) = self.config_file_path {
+ let default_config =
+ Config::from_default_assets().unwrap_or_else(|_| Default::default());
+ Config::from_path(path, Some(default_config.clone()))
+ .unwrap_or_else(|_| default_config)
+ } else {
+ Config::from_default_assets().unwrap_or_else(|_| Default::default())
+ }
+ };
+
+ let (mut layout, mut config_with_merged_layout_opts) = {
+ let layout_dir = self
+ .configuration_options
+ .as_ref()
+ .and_then(|e| e.layout_dir.clone())
+ .or_else(|| config.options.layout_dir.clone())
+ .or_else(|| {
+ self.config_dir
+ .clone()
+ .or_else(find_default_config_dir)
+ .map(|dir| dir.join("layouts"))
+ });
+ self.layout.as_ref().and_then(|layout_info| {
+ Layout::from_layout_info_with_config(&layout_dir, layout_info, Some(config.clone()))
+ .ok()
+ })
+ }
+ .map(|(layout, config)| (layout, config))
+ .unwrap_or_else(|| (Layout::default_layout_asset(), config));
+
+ if self.force_run_layout_commands {
+ layout.recursively_add_start_suspended(Some(false));
+ }
+
+ config_with_merged_layout_opts.themes = config_with_merged_layout_opts
+ .themes
+ .merge(get_default_themes());
+
+ let user_theme_dir = self
+ .configuration_options
+ .as_ref()
+ .and_then(|o| o.theme_dir.clone())
+ .or_else(|| {
+ get_theme_dir(config_with_merged_layout_opts.options.theme_dir.clone())
+ .or_else(find_default_config_dir)
+ .filter(|dir| dir.exists())
+ });
+ if let Some(themes) = user_theme_dir.and_then(|u| Themes::from_dir(u).ok()) {
+ config_with_merged_layout_opts.themes =
+ config_with_merged_layout_opts.themes.merge(themes);
+ }
+
+ (config_with_merged_layout_opts, layout)
+ }
+}
diff --git a/zellij-utils/src/input/config.rs b/zellij-utils/src/input/config.rs
index 287d0e5f..1f407621 100644
--- a/zellij-utils/src/input/config.rs
+++ b/zellij-utils/src/input/config.rs
@@ -19,7 +19,7 @@ use crate::cli::{CliArgs, Command};
use crate::envs::EnvironmentVariables;
use crate::{home, setup};
-const DEFAULT_CONFIG_FILE_NAME: &str = "config.kdl";
+pub const DEFAULT_CONFIG_FILE_NAME: &str = "config.kdl";
type ConfigResult = Result<Config, ConfigError>;
@@ -243,26 +243,29 @@ impl Config {
opts.config.clone().or_else(|| {
opts.config_dir
.clone()
- .or_else(home::find_default_config_dir)
+ .or_else(|| {
+ home::try_create_home_config_dir();
+ home::find_default_config_dir()
+ })
.map(|config_dir| config_dir.join(DEFAULT_CONFIG_FILE_NAME))
})
}
pub fn default_config_file_path() -> Option<PathBuf> {
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>> {
+ pub fn write_config_to_disk(
+ config: String,
+ config_file_path: &PathBuf,
+ ) -> Result<Config, Option<PathBuf>> {
// if we fail, try to return the PathBuf of the file we were not able to write to
+ let config_file_path = config_file_path.clone();
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 backed_up_file_name = Config::backup_current_config(&config_file_path)?;
let config = match backed_up_file_name {
Some(backed_up_file_name) => {
format!(
@@ -295,31 +298,28 @@ impl Config {
})
}
// 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 {
- if opts.config.is_none() {
- // if a config file path wasn't explicitly specified, we try to create the default
- // config folder
- home::try_create_home_config_dir();
- }
- match Config::config_file_path(opts) {
- Some(config_file_path) => {
- if config_file_path.exists() {
+ pub fn write_config_to_disk_if_it_does_not_exist(
+ config: String,
+ config_file_path: &Option<PathBuf>,
+ ) -> bool {
+ let Some(config_file_path) = config_file_path.clone() else {
+ log::error!("Could not find file path to write config");
+ return false;
+ };
+ 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
- } 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> {
@@ -361,47 +361,45 @@ impl Config {
},
}
}
- fn backup_current_config(opts: &CliArgs) -> Result<Option<PathBuf>, Option<PathBuf>> {
+ fn backup_current_config(
+ config_file_path: &PathBuf,
+ ) -> 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)
+ // 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.clone()))
+ }
+ },
}
}
fn autogen_config_message(backed_up_file_name: PathBuf) -> String {
diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs
index e29ad94d..83704439 100644
--- a/zellij-utils/src/input/layout.rs
+++ b/zellij-utils/src/input/layout.rs
@@ -1248,6 +1248,50 @@ impl Layout {
}
layout
}
+ pub fn from_layout_info_with_config(
+ layout_dir: &Option<PathBuf>,
+ layout_info: &LayoutInfo,
+ config: Option<Config>,
+ ) -> Result<(Layout, Config), ConfigError> {
+ let mut should_start_layout_commands_suspended = false;
+ let (path_to_raw_layout, raw_layout, raw_swap_layouts) = match layout_info {
+ LayoutInfo::File(layout_name_without_extension) => {
+ let layout_dir = layout_dir.clone().or_else(|| default_layout_dir());
+ let (path_to_layout, stringified_layout, swap_layouts) =
+ Self::stringified_from_dir(
+ &PathBuf::from(layout_name_without_extension),
+ layout_dir.as_ref(),
+ )?;
+ (Some(path_to_layout), stringified_layout, swap_layouts)
+ },
+ LayoutInfo::BuiltIn(layout_name) => {
+ let (path_to_layout, stringified_layout, swap_layouts) =
+ Self::stringified_from_default_assets(&PathBuf::from(layout_name))?;
+ (Some(path_to_layout), stringified_layout, swap_layouts)
+ },
+ LayoutInfo::Url(url) => {
+ should_start_layout_commands_suspended = true;
+ (Some(url.clone()), Self::stringified_from_url(&url)?, None)
+ },
+ LayoutInfo::Stringified(stringified_layout) => (None, stringified_layout.clone(), None),
+ };
+ let mut layout = Layout::from_kdl(
+ &raw_layout,
+ path_to_raw_layout,
+ raw_swap_layouts
+ .as_ref()
+ .map(|(r, f)| (r.as_str(), f.as_str())),
+ None,
+ );
+ if should_start_layout_commands_suspended {
+ layout
+ .iter_mut()
+ .next()
+ .map(|l| l.recursively_add_start_suspended_including_template(Some(true)));
+ }
+ let config = Config::from_kdl(&raw_layout, config)?; // this merges the two config, with
+ layout.map(|l| (l, config))
+ }
pub fn stringified_from_path_or_default(
layout_path: Option<&PathBuf>,
layout_dir: Option<PathBuf>,
@@ -1369,6 +1413,20 @@ impl Layout {
let config = Config::from_kdl(&raw_layout, Some(config))?; // this merges the two config, with
Ok((layout, config))
}
+ pub fn default_layout_asset() -> Layout {
+ Layout::from_kdl(
+ &Self::stringified_default_from_assets().unwrap(),
+ None,
+ Some((
+ "",
+ Self::stringified_default_swap_from_assets()
+ .unwrap()
+ .as_str(),
+ )),
+ None,
+ )
+ .unwrap()
+ }
pub fn from_str(
raw: &str,
path_to_raw_layout: String,
diff --git a/zellij-utils/src/input/mod.rs b/zellij-utils/src/input/mod.rs
index 2ff8c206..69520fe2 100644
--- a/zellij-utils/src/input/mod.rs
+++ b/zellij-utils/src/input/mod.rs
@@ -1,4 +1,5 @@
pub mod actions;
+pub mod cli_assets;
pub mod command;
pub mod config;
pub mod keybinds;
diff --git a/zellij-utils/src/input/options.rs b/zellij-utils/src/input/options.rs
index 72b4e63b..e9035dc1 100644
--- a/zellij-utils/src/input/options.rs
+++ b/zellij-utils/src/input/options.rs
@@ -496,73 +496,3 @@ impl Options {
}
}
}
-
-#[derive(Clone, Default, Debug, PartialEq, Args, Serialize, Deserialize)]
-/// Options that can be set through cli flags
-/// boolean flags end up toggling boolean options in `Options`
-pub struct CliOptions {
- /// Disable handling of mouse events
- #[clap(long, conflicts_with("mouse-mode"), value_parser)]
- pub disable_mouse_mode: bool,
- /// Disable display of pane frames
- #[clap(long, conflicts_with("pane-frames"), value_parser)]
- pub no_pane_frames: bool,
- #[clap(flatten)]
- pub options: Options,
-}
-
-impl From<CliOptions> for Options {
- fn from(cli_options: CliOptions) -> Self {
- let mut opts = cli_options.options;
-
- // TODO: what?
- if cli_options.no_pane_frames {
- opts.pane_frames = Some(false);
- }
- if cli_options.disable_mouse_mode {
- opts.mouse_mode = Some(false);
- }
-
- Self {
- simplified_ui: opts.simplified_ui,
- theme: opts.theme,
- default_mode: opts.default_mode,
- default_shell: opts.default_shell,
- default_cwd: opts.default_cwd,
- default_layout: opts.default_layout,
- layout_dir: opts.layout_dir,
- theme_dir: opts.theme_dir,
- mouse_mode: opts.mouse_mode,
- pane_frames: opts.pane_frames,
- mirror_session: opts.mirror_session,
- on_force_close: opts.on_force_close,
- scroll_buffer_size: opts.scroll_buffer_size,
- copy_command: opts.copy_command,
- copy_clipboard: opts.copy_clipboard,
- copy_on_select: opts.copy_on_select,
- scrollback_editor: opts.scrollback_editor,
- session_name: opts.session_name,
- attach_to_session: opts.attach_to_session,
- auto_layout: opts.auto_layout,
- session_serialization: opts.session_serialization,
- serialize_pane_viewport: opts.serialize_pane_viewport,
- scrollback_lines_to_serialize: opts.scrollback_lines_to_serialize,
- styled_underlines: opts.styled_underlines,
- serialization_interval: opts.serialization_interval,
- support_kitty_keyboard_protocol: opts.support_kitty_keyboard_protocol,
- web_server: opts.web_server,
- web_sharing: opts.web_sharing,
- stacked_resize: opts.stacked_resize,
- show_startup_tips: opts.show_startup_tips,
- show_release_notes: opts.show_release_notes,
- advanced_mouse_actions: opts.advanced_mouse_actions,
- web_server_ip: opts.web_server_ip,
- web_server_port: opts.web_server_port,
- web_server_cert: opts.web_server_cert,
- web_server_key: opts.web_server_key,
- enforce_https_for_localhost: opts.enforce_https_for_localhost,
- post_command_discovery_hook: opts.post_command_discovery_hook,
- ..Default::default()
- }
- }
-}
diff --git a/zellij-utils/src/ipc.rs b/zellij-utils/src/ipc.rs
index 2ec6d8d5..57a31cd0 100644
--- a/zellij-utils/src/ipc.rs
+++ b/zellij-utils/src/ipc.rs
@@ -1,10 +1,8 @@
//! IPC stuff for starting to split things into a client and server model.
use crate::{
- cli::CliArgs,
data::{ClientId, ConnectToSession, KeyWithModifier, Style},
errors::{get_current_ctx, prelude::*, ErrorContext},
- input::config::Config,
- input::{actions::Action, layout::Layout, options::Options, plugins::PluginAliases},
+ input::{actions::Action, cli_assets::CliAssets},
pane_size::{Size, SizeInPixels},
};
use interprocess::local_socket::LocalSocketStream;
@@ -16,7 +14,6 @@ use std::{
io::{self, Write},
marker::PhantomData,
os::unix::io::{AsRawFd, FromRawFd},
- path::PathBuf,
};
type SessionId = u64;
@@ -71,21 +68,12 @@ pub enum ClientToServerMsg {
ForegroundColor(String),
ColorRegisters(Vec<(usize, String)>),
TerminalResize(Size),
- NewClient(
- ClientAttributes,
- Box<CliArgs>,
- Box<Config>, // represents the saved configuration
- Box<Options>, // represents the runtime configuration
- Box<Layout>,
- Box<PluginAliases>,
- bool, // should launch setup wizard
+ FirstClientConnected(
+ CliAssets,
bool, // is_web_client
- bool, // layout_is_welcome_screen
),
AttachClient(
- ClientAttributes,
- Config, // represents the saved configuration
- Options, // represents the runtime configuration
+ CliAssets,
Option<usize>, // tab position to focus
Option<(u32, bool)>, // (pane_id, is_plugin) => pane id to focus
bool, // is_web_client
@@ -95,8 +83,6 @@ pub enum ClientToServerMsg {
ClientExited,
KillSession,
ConnStatus,
- ConfigWrittenToDisk(Config),
- FailedToWriteConfigToDisk(Option<PathBuf>),
WebServerStarted(String), // String -> base_url
FailedToStartWebServer(String),
}
@@ -114,9 +100,9 @@ pub enum ServerToClientMsg {
UnblockCliPipeInput(String), // String -> pipe name
CliPipeOutput(String, String), // String -> pipe name, String -> Output
QueryTerminalSize,
- WriteConfigToDisk { config: String },
StartWebServer,
RenamedSession(String), // String -> new session name
+ ConfigFileUpdated,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
diff --git a/zellij-utils/src/setup.rs b/zellij-utils/src/setup.rs
index 0093cbfa..2be6cdef 100644
--- a/zellij-utils/src/setup.rs
+++ b/zellij-utils/src/setup.rs
@@ -71,7 +71,7 @@ pub fn get_default_data_dir() -> PathBuf {
}
#[cfg(not(test))]
-fn get_default_themes() -> Themes {
+pub fn get_default_themes() -> Themes {
let mut themes = Themes::default();
for file in ZELLIJ_DEFAULT_THEMES.files() {
if let Some(content) = file.contents_utf8() {
@@ -86,7 +86,7 @@ fn get_default_themes() -> Themes {
}
#[cfg(test)]
-fn get_default_themes() -> Themes {
+pub fn get_default_themes() -> Themes {
Themes::default()
}
@@ -744,7 +744,7 @@ fn merge_attach_command_options(
mod setup_test {
use super::Setup;
use crate::cli::{CliArgs, Command};
- use crate::input::options::{CliOptions, Options};
+ use crate::input::options::Options;
use insta::assert_snapshot;
use std::path::PathBuf;
@@ -759,11 +759,8 @@ mod setup_test {
#[test]
fn cli_arguments_override_config_options() {
let mut cli_args = CliArgs::default();
- cli_args.command = Some(Command::Options(CliOptions {
- options: Options {
- simplified_ui: Some(true),
- ..Default::default()
- },
+ cli_args.command = Some(Command::Options(Options {
+ simplified_ui: Some(true),
..Default::default()
}));
let (_config, _layout, options, _, _) = Setup::from_cli_args(&cli_args).unwrap();
@@ -787,11 +784,8 @@ mod setup_test {
"{}/src/test-fixtures/layout-with-options.kdl",
env!("CARGO_MANIFEST_DIR")
)));
- cli_args.command = Some(Command::Options(CliOptions {
- options: Options {
- pane_frames: Some(true),
- ..Default::default()
- },
+ cli_args.command = Some(Command::Options(Options {
+ pane_frames: Some(true),
..Default::default()
}));
let (_config, layout, options, _, _) = Setup::from_cli_args(&cli_args).unwrap();
diff --git a/zellij-utils/src/web_server_commands.rs b/zellij-utils/src/web_server_commands.rs
index e2125eac..0b020f6e 100644
--- a/zellij-utils/src/web_server_commands.rs
+++ b/zellij-utils/src/web_server_commands.rs
@@ -1,6 +1,5 @@
use crate::consts::WEBSERVER_SOCKET_PATH;
use crate::errors::prelude::*;
-use crate::input::config::Config;
use interprocess::local_socket::LocalSocketStream;
use serde::{Deserialize, Serialize};
use std::fs;
@@ -41,7 +40,6 @@ pub fn shutdown_all_webserver_instances() -> Result<()> {
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum InstructionForWebServer {
ShutdownWebServer,
- ConfigWrittenToDisk(Config),
}
pub fn create_webserver_sender(path: &str) -> Result<BufWriter<LocalSocketStream>> {