fix(serialization): don't serialize when only UI elements present and provide post command discovery hook (#4276)
* do not serialize when only UI elements are present * start work on a post serialization hook * add post_command_discovery_hook * fix tests * style(fmt): rustfmt * some cleanups * moar formatting * docs(changelog): add PR
This commit is contained in:
parent
358caa180c
commit
da9cf4ffeb
22 changed files with 178 additions and 15 deletions
|
|
@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
|||
* fix: support multiline hyperlinks (https://github.com/zellij-org/zellij/pull/4264)
|
||||
* fix: use terminal title when spawning terminal panes from plugin (https://github.com/zellij-org/zellij/pull/4272)
|
||||
* fix: allow specifying CWD for tabs without necessitating a layout (https://github.com/zellij-org/zellij/pull/4273)
|
||||
* fix: don't serialize when only ui elements present and provide post command disovery hook (https://github.com/zellij-org/zellij/pull/4276)
|
||||
|
||||
## [0.42.2] - 2025-04-15
|
||||
* refactor(terminal): track scroll_region as tuple rather than Option (https://github.com/zellij-org/zellij/pull/4082)
|
||||
|
|
|
|||
|
|
@ -237,7 +237,9 @@ pub(crate) fn background_jobs_main(
|
|||
.unwrap_or(DEFAULT_SERIALIZATION_INTERVAL)
|
||||
.into()
|
||||
{
|
||||
let _ = senders.send_to_screen(ScreenInstruction::DumpLayoutToHd);
|
||||
let _ = senders.send_to_screen(
|
||||
ScreenInstruction::SerializeLayoutForResurrection,
|
||||
);
|
||||
*last_serialization_time.lock().unwrap() = Instant::now();
|
||||
}
|
||||
task::sleep(std::time::Duration::from_millis(SESSION_READ_DURATION))
|
||||
|
|
|
|||
|
|
@ -414,6 +414,7 @@ impl SessionMetaData {
|
|||
.send_to_pty(PtyInstruction::Reconfigure {
|
||||
client_id,
|
||||
default_editor: new_config.options.scrollback_editor,
|
||||
post_command_discovery_hook: new_config.options.post_command_discovery_hook,
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
|
@ -1512,6 +1513,7 @@ fn init_session(
|
|||
),
|
||||
opts.debug,
|
||||
config_options.scrollback_editor.clone(),
|
||||
config_options.post_command_discovery_hook.clone(),
|
||||
);
|
||||
|
||||
move || pty_thread_main(pty, layout.clone()).fatal()
|
||||
|
|
|
|||
|
|
@ -512,7 +512,7 @@ pub trait ServerOsApi: Send + Sync {
|
|||
HashMap::new()
|
||||
}
|
||||
/// Get a list of all running commands by their parent process id
|
||||
fn get_all_cmds_by_ppid(&self) -> HashMap<String, Vec<String>> {
|
||||
fn get_all_cmds_by_ppid(&self, _post_hook: &Option<String>) -> HashMap<String, Vec<String>> {
|
||||
HashMap::new()
|
||||
}
|
||||
/// Writes the given buffer to a string
|
||||
|
|
@ -777,7 +777,7 @@ impl ServerOsApi for ServerOsInputOutput {
|
|||
|
||||
cwds
|
||||
}
|
||||
fn get_all_cmds_by_ppid(&self) -> HashMap<String, Vec<String>> {
|
||||
fn get_all_cmds_by_ppid(&self, post_hook: &Option<String>) -> HashMap<String, Vec<String>> {
|
||||
// the key is the stringified ppid
|
||||
let mut cmds = HashMap::new();
|
||||
if let Some(output) = Command::new("ps")
|
||||
|
|
@ -796,7 +796,28 @@ impl ServerOsApi for ServerOsInputOutput {
|
|||
let mut line_parts = line_parts.into_iter();
|
||||
let ppid = line_parts.next();
|
||||
if let Some(ppid) = ppid {
|
||||
cmds.insert(ppid.into(), line_parts.collect());
|
||||
match &post_hook {
|
||||
Some(post_hook) => {
|
||||
let command: Vec<String> = line_parts.clone().collect();
|
||||
let stringified = command.join(" ");
|
||||
let cmd = match run_command_hook(&stringified, post_hook) {
|
||||
Ok(command) => command,
|
||||
Err(e) => {
|
||||
log::error!("Post command hook failed to run: {}", e);
|
||||
stringified.to_owned()
|
||||
},
|
||||
};
|
||||
let line_parts: Vec<String> = cmd
|
||||
.trim()
|
||||
.split_ascii_whitespace()
|
||||
.map(|p| p.to_owned())
|
||||
.collect();
|
||||
cmds.insert(ppid.into(), line_parts);
|
||||
},
|
||||
None => {
|
||||
cmds.insert(ppid.into(), line_parts.collect());
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -930,6 +951,22 @@ pub struct ChildId {
|
|||
pub shell: Option<Pid>,
|
||||
}
|
||||
|
||||
fn run_command_hook(
|
||||
original_command: &str,
|
||||
hook_script: &str,
|
||||
) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let output = Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg(hook_script)
|
||||
.env("RESURRECT_COMMAND", original_command)
|
||||
.output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(format!("Hook failed: {}", String::from_utf8_lossy(&output.stderr)).into());
|
||||
}
|
||||
Ok(String::from_utf8(output.stdout)?.trim().to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "./unit/os_input_output_tests.rs"]
|
||||
mod os_input_output_tests;
|
||||
|
|
|
|||
|
|
@ -173,6 +173,7 @@ pub enum PtyInstruction {
|
|||
Reconfigure {
|
||||
client_id: ClientId,
|
||||
default_editor: Option<PathBuf>,
|
||||
post_command_discovery_hook: Option<String>,
|
||||
},
|
||||
ListClientsToPlugin(SessionLayoutMetadata, PluginId, ClientId),
|
||||
Exit,
|
||||
|
|
@ -211,6 +212,7 @@ pub(crate) struct Pty {
|
|||
debug_to_file: bool,
|
||||
task_handles: HashMap<u32, JoinHandle<()>>, // terminal_id to join-handle
|
||||
default_editor: Option<PathBuf>,
|
||||
post_command_discovery_hook: Option<String>,
|
||||
}
|
||||
|
||||
pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
|
||||
|
|
@ -726,9 +728,10 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
|
|||
},
|
||||
PtyInstruction::Reconfigure {
|
||||
default_editor,
|
||||
post_command_discovery_hook,
|
||||
client_id: _,
|
||||
} => {
|
||||
pty.reconfigure(default_editor);
|
||||
pty.reconfigure(default_editor, post_command_discovery_hook);
|
||||
},
|
||||
PtyInstruction::Exit => break,
|
||||
}
|
||||
|
|
@ -741,6 +744,7 @@ impl Pty {
|
|||
bus: Bus<PtyInstruction>,
|
||||
debug_to_file: bool,
|
||||
default_editor: Option<PathBuf>,
|
||||
post_command_discovery_hook: Option<String>,
|
||||
) -> Self {
|
||||
Pty {
|
||||
active_panes: HashMap::new(),
|
||||
|
|
@ -750,6 +754,7 @@ impl Pty {
|
|||
task_handles: HashMap::new(),
|
||||
default_editor,
|
||||
originating_plugins: HashMap::new(),
|
||||
post_command_discovery_hook,
|
||||
}
|
||||
}
|
||||
pub fn get_default_terminal(
|
||||
|
|
@ -1430,7 +1435,7 @@ impl Pty {
|
|||
.bus
|
||||
.os_input
|
||||
.as_ref()
|
||||
.map(|os_input| os_input.get_all_cmds_by_ppid())
|
||||
.map(|os_input| os_input.get_all_cmds_by_ppid(&self.post_command_discovery_hook))
|
||||
.unwrap_or_default();
|
||||
|
||||
for terminal_id in terminal_ids {
|
||||
|
|
@ -1505,8 +1510,13 @@ impl Pty {
|
|||
))?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn reconfigure(&mut self, default_editor: Option<PathBuf>) {
|
||||
pub fn reconfigure(
|
||||
&mut self,
|
||||
default_editor: Option<PathBuf>,
|
||||
post_command_discovery_hook: Option<String>,
|
||||
) {
|
||||
self.default_editor = default_editor;
|
||||
self.post_command_discovery_hook = post_command_discovery_hook;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -353,7 +353,7 @@ pub enum ScreenInstruction {
|
|||
bool, // close replaced pane
|
||||
ClientTabIndexOrPaneId,
|
||||
),
|
||||
DumpLayoutToHd,
|
||||
SerializeLayoutForResurrection,
|
||||
RenameSession(String, ClientId), // String -> new name
|
||||
ListClientsMetadata(Option<PathBuf>, ClientId), // Option<PathBuf> - default shell
|
||||
Reconfigure {
|
||||
|
|
@ -595,7 +595,9 @@ impl From<&ScreenInstruction> for ScreenContext {
|
|||
ScreenInstruction::UpdateSessionInfos(..) => ScreenContext::UpdateSessionInfos,
|
||||
ScreenInstruction::ReplacePane(..) => ScreenContext::ReplacePane,
|
||||
ScreenInstruction::NewInPlacePluginPane(..) => ScreenContext::NewInPlacePluginPane,
|
||||
ScreenInstruction::DumpLayoutToHd => ScreenContext::DumpLayoutToHd,
|
||||
ScreenInstruction::SerializeLayoutForResurrection => {
|
||||
ScreenContext::SerializeLayoutForResurrection
|
||||
},
|
||||
ScreenInstruction::RenameSession(..) => ScreenContext::RenameSession,
|
||||
ScreenInstruction::ListClientsMetadata(..) => ScreenContext::ListClientsMetadata,
|
||||
ScreenInstruction::Reconfigure { .. } => ScreenContext::Reconfigure,
|
||||
|
|
@ -5047,7 +5049,7 @@ pub(crate) fn screen_thread_main(
|
|||
|
||||
screen.render(None)?;
|
||||
},
|
||||
ScreenInstruction::DumpLayoutToHd => {
|
||||
ScreenInstruction::SerializeLayoutForResurrection => {
|
||||
if screen.session_serialization {
|
||||
screen.dump_layout_to_hd()?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,15 +144,40 @@ impl SessionLayoutMetadata {
|
|||
fn pane_count(&self) -> usize {
|
||||
let mut pane_count = 0;
|
||||
for tab in &self.tabs {
|
||||
for _tiled_pane in &tab.tiled_panes {
|
||||
pane_count += 1;
|
||||
for tiled_pane in &tab.tiled_panes {
|
||||
if !self.should_exclude_from_count(tiled_pane) {
|
||||
pane_count += 1;
|
||||
}
|
||||
}
|
||||
for _floating_pane in &tab.floating_panes {
|
||||
pane_count += 1;
|
||||
for floating_pane in &tab.floating_panes {
|
||||
if !self.should_exclude_from_count(floating_pane) {
|
||||
pane_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
pane_count
|
||||
}
|
||||
fn should_exclude_from_count(&self, pane: &PaneLayoutMetadata) -> bool {
|
||||
if let Some(Run::Plugin(run_plugin)) = &pane.run {
|
||||
let location_string = run_plugin.location_string();
|
||||
if location_string == "zellij:about" {
|
||||
return true;
|
||||
}
|
||||
if location_string == "zellij:session-manager" {
|
||||
return true;
|
||||
}
|
||||
if location_string == "zellij:plugin-manager" {
|
||||
return true;
|
||||
}
|
||||
if location_string == "zellij:configuration-manager" {
|
||||
return true;
|
||||
}
|
||||
if location_string == "zellij:share" {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
fn is_default_shell(
|
||||
default_shell: Option<&PathBuf>,
|
||||
command_name: &String,
|
||||
|
|
|
|||
|
|
@ -506,6 +506,15 @@ load_plugins {
|
|||
//
|
||||
// advanced_mouse_actions false
|
||||
|
||||
// A command to run (will be wrapped with sh -c and provided the RESURRECT_COMMAND env variable)
|
||||
// after Zellij attempts to discover a command inside a pane when resurrecting sessions, the STDOUT
|
||||
// of this command will be used instead of the discovered RESURRECT_COMMAND
|
||||
// can be useful for removing wrappers around commands
|
||||
// Note: be sure to escape backslashes and similar characters properly
|
||||
//
|
||||
// post_command_discovery_hook "echo $RESURRECT_COMMAND | sed <your_regex_here>"
|
||||
|
||||
|
||||
web_client {
|
||||
font "monospace"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -348,7 +348,7 @@ pub enum ScreenContext {
|
|||
UpdateSessionInfos,
|
||||
ReplacePane,
|
||||
NewInPlacePluginPane,
|
||||
DumpLayoutToHd,
|
||||
SerializeLayoutForResurrection,
|
||||
RenameSession,
|
||||
DumpLayoutToPlugin,
|
||||
ListClientsMetadata,
|
||||
|
|
|
|||
|
|
@ -227,6 +227,10 @@ pub struct Options {
|
|||
pub web_server_cert: Option<PathBuf>,
|
||||
pub web_server_key: Option<PathBuf>,
|
||||
pub enforce_https_for_localhost: Option<bool>,
|
||||
/// A command to run after the discovery of running commands when serializing, for the purpose
|
||||
/// of manipulating the command (eg. with a regex) before it gets serialized
|
||||
#[clap(long, value_parser)]
|
||||
pub post_command_discovery_hook: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(ArgEnum, Deserialize, Serialize, Debug, Clone, Copy, PartialEq)]
|
||||
|
|
@ -320,6 +324,9 @@ impl Options {
|
|||
let enforce_https_for_localhost = other
|
||||
.enforce_https_for_localhost
|
||||
.or(self.enforce_https_for_localhost);
|
||||
let post_command_discovery_hook = other
|
||||
.post_command_discovery_hook
|
||||
.or(self.post_command_discovery_hook.clone());
|
||||
|
||||
Options {
|
||||
simplified_ui,
|
||||
|
|
@ -360,6 +367,7 @@ impl Options {
|
|||
web_server_cert,
|
||||
web_server_key,
|
||||
enforce_https_for_localhost,
|
||||
post_command_discovery_hook,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -433,6 +441,9 @@ impl Options {
|
|||
let enforce_https_for_localhost = other
|
||||
.enforce_https_for_localhost
|
||||
.or(self.enforce_https_for_localhost);
|
||||
let post_command_discovery_hook = other
|
||||
.post_command_discovery_hook
|
||||
.or_else(|| self.post_command_discovery_hook.clone());
|
||||
|
||||
Options {
|
||||
simplified_ui,
|
||||
|
|
@ -473,6 +484,7 @@ impl Options {
|
|||
web_server_cert,
|
||||
web_server_key,
|
||||
enforce_https_for_localhost,
|
||||
post_command_discovery_hook,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -549,6 +561,7 @@ impl From<CliOptions> for Options {
|
|||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2399,6 +2399,9 @@ impl Options {
|
|||
let enforce_https_for_localhost =
|
||||
kdl_property_first_arg_as_bool_or_error!(kdl_options, "enforce_https_for_localhost")
|
||||
.map(|(v, _)| v);
|
||||
let post_command_discovery_hook =
|
||||
kdl_property_first_arg_as_string_or_error!(kdl_options, "post_command_discovery_hook")
|
||||
.map(|(hook, _entry)| hook.to_string());
|
||||
|
||||
Ok(Options {
|
||||
simplified_ui,
|
||||
|
|
@ -2439,6 +2442,7 @@ impl Options {
|
|||
web_server_cert,
|
||||
web_server_key,
|
||||
enforce_https_for_localhost,
|
||||
post_command_discovery_hook,
|
||||
})
|
||||
}
|
||||
pub fn from_string(stringified_keybindings: &String) -> Result<Self, ConfigError> {
|
||||
|
|
@ -3562,6 +3566,36 @@ impl Options {
|
|||
None
|
||||
}
|
||||
}
|
||||
fn post_command_discovery_hook_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
|
||||
let comment_text = format!(
|
||||
"{}\n{}\n{}\n{}\n{}\n{}",
|
||||
" ",
|
||||
"// A command to run (will be wrapped with sh -c and provided the RESURRECT_COMMAND env variable) ",
|
||||
"// after Zellij attempts to discover a command inside a pane when resurrecting sessions, the STDOUT",
|
||||
"// of this command will be used instead of the discovered RESURRECT_COMMAND",
|
||||
"// can be useful for removing wrappers around commands",
|
||||
"// Note: be sure to escape backslashes and similar characters properly",
|
||||
);
|
||||
|
||||
let create_node = |node_value: &str| -> KdlNode {
|
||||
let mut node = KdlNode::new("post_command_discovery_hook");
|
||||
node.push(node_value.to_owned());
|
||||
node
|
||||
};
|
||||
if let Some(post_command_discovery_hook) = &self.post_command_discovery_hook {
|
||||
let mut node = create_node(&post_command_discovery_hook);
|
||||
if add_comments {
|
||||
node.set_leading(format!("{}\n", comment_text));
|
||||
}
|
||||
Some(node)
|
||||
} else if add_comments {
|
||||
let mut node = create_node("echo $RESURRECT_COMMAND | sed <your_regex_here>");
|
||||
node.set_leading(format!("{}\n// ", comment_text));
|
||||
Some(node)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub fn to_kdl(&self, add_comments: bool) -> Vec<KdlNode> {
|
||||
let mut nodes = vec![];
|
||||
if let Some(simplified_ui_node) = self.simplified_ui_to_kdl(add_comments) {
|
||||
|
|
@ -3684,6 +3718,11 @@ impl Options {
|
|||
if let Some(web_server_port) = self.web_server_port_to_kdl(add_comments) {
|
||||
nodes.push(web_server_port);
|
||||
}
|
||||
if let Some(post_command_discovery_hook) =
|
||||
self.post_command_discovery_hook_to_kdl(add_comments)
|
||||
{
|
||||
nodes.push(post_command_discovery_hook);
|
||||
}
|
||||
nodes
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -527,3 +527,10 @@ web_client {
|
|||
// Default: 8082
|
||||
// (Requires restart)
|
||||
// web_server_port 8082
|
||||
|
||||
// A command to run (will be wrapped with sh -c and provided the RESURRECT_COMMAND env variable)
|
||||
// after Zellij attempts to discover a command inside a pane when resurrecting sessions, the STDOUT
|
||||
// of this command will be used instead of the discovered RESURRECT_COMMAND
|
||||
// can be useful for removing wrappers around commands
|
||||
// Note: be sure to escape backslashes and similar characters properly
|
||||
// post_command_discovery_hook "echo $RESURRECT_COMMAND | sed <your_regex_here>"
|
||||
|
|
|
|||
|
|
@ -254,3 +254,10 @@ web_sharing "disabled"
|
|||
// Default: 8082
|
||||
// (Requires restart)
|
||||
// web_server_port 8082
|
||||
|
||||
// A command to run (will be wrapped with sh -c and provided the RESURRECT_COMMAND env variable)
|
||||
// after Zellij attempts to discover a command inside a pane when resurrecting sessions, the STDOUT
|
||||
// of this command will be used instead of the discovered RESURRECT_COMMAND
|
||||
// can be useful for removing wrappers around commands
|
||||
// Note: be sure to escape backslashes and similar characters properly
|
||||
// post_command_discovery_hook "echo $RESURRECT_COMMAND | sed <your_regex_here>"
|
||||
|
|
|
|||
|
|
@ -43,4 +43,5 @@ Options {
|
|||
web_server_cert: None,
|
||||
web_server_key: None,
|
||||
enforce_https_for_localhost: None,
|
||||
post_command_discovery_hook: None,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,4 +43,5 @@ Options {
|
|||
web_server_cert: None,
|
||||
web_server_key: None,
|
||||
enforce_https_for_localhost: None,
|
||||
post_command_discovery_hook: None,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,4 +41,5 @@ Options {
|
|||
web_server_cert: None,
|
||||
web_server_key: None,
|
||||
enforce_https_for_localhost: None,
|
||||
post_command_discovery_hook: None,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5969,6 +5969,7 @@ Config {
|
|||
web_server_cert: None,
|
||||
web_server_key: None,
|
||||
enforce_https_for_localhost: None,
|
||||
post_command_discovery_hook: None,
|
||||
},
|
||||
themes: {},
|
||||
plugins: PluginAliases {
|
||||
|
|
|
|||
|
|
@ -5969,6 +5969,7 @@ Config {
|
|||
web_server_cert: None,
|
||||
web_server_key: None,
|
||||
enforce_https_for_localhost: None,
|
||||
post_command_discovery_hook: None,
|
||||
},
|
||||
themes: {},
|
||||
plugins: PluginAliases {
|
||||
|
|
|
|||
|
|
@ -128,6 +128,7 @@ Config {
|
|||
web_server_cert: None,
|
||||
web_server_key: None,
|
||||
enforce_https_for_localhost: None,
|
||||
post_command_discovery_hook: None,
|
||||
},
|
||||
themes: {},
|
||||
plugins: PluginAliases {
|
||||
|
|
|
|||
|
|
@ -43,4 +43,5 @@ Options {
|
|||
web_server_cert: None,
|
||||
web_server_key: None,
|
||||
enforce_https_for_localhost: None,
|
||||
post_command_discovery_hook: None,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5969,6 +5969,7 @@ Config {
|
|||
web_server_cert: None,
|
||||
web_server_key: None,
|
||||
enforce_https_for_localhost: None,
|
||||
post_command_discovery_hook: None,
|
||||
},
|
||||
themes: {
|
||||
"other-theme-from-config": Theme {
|
||||
|
|
|
|||
|
|
@ -5969,6 +5969,7 @@ Config {
|
|||
web_server_cert: None,
|
||||
web_server_key: None,
|
||||
enforce_https_for_localhost: None,
|
||||
post_command_discovery_hook: None,
|
||||
},
|
||||
themes: {},
|
||||
plugins: PluginAliases {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue