#![cfg(feature = "daemon")]
use std::path::PathBuf;
use crate::daemon::paths::CachePaths;
use crate::daemon::shard::{list_shards, read_canonical_shard, CanonicalShard};
use crate::exec::{AutoloadFlags, ShellExecutor, zstyle_entry};
pub fn apply_all(executor: &mut ShellExecutor) -> usize {
let t0 = std::time::Instant::now();
let paths = match CachePaths::resolve() {
Ok(p) => p,
Err(e) => {
tracing::warn!(error = %e, "canonical_apply: cache paths unresolved");
return 0;
}
};
let shard_path = match latest_recorder_shard(&paths) {
Some(p) => p,
None => {
tracing::info!(
"canonical_apply: no recorder shard found in {} — vanilla fallback",
paths.images.display()
);
return 0;
}
};
let shard = match read_canonical_shard(&shard_path) {
Ok(s) => s,
Err(e) => {
tracing::warn!(error = %e, path = %shard_path.display(), "canonical_apply: shard read failed");
return 0;
}
};
let total = apply_shard(executor, shard);
let elapsed_us = t0.elapsed().as_micros();
tracing::info!(
rows = total,
elapsed_us,
path = %shard_path.display(),
"canonical state applied from rkyv shard (no IPC)"
);
total
}
fn latest_recorder_shard(paths: &CachePaths) -> Option<PathBuf> {
let entries = list_shards(paths).ok()?;
entries
.into_iter()
.filter(|p| {
p.file_name()
.and_then(|s| s.to_str())
.map(|s| s.ends_with("-recorder.rkyv"))
.unwrap_or(false)
})
.max_by_key(|p| {
std::fs::metadata(p)
.and_then(|m| m.modified())
.ok()
})
}
fn apply_shard(executor: &mut ShellExecutor, shard: CanonicalShard) -> usize {
let mut total = 0;
for (n, v) in shard.aliases {
executor.set_alias(n, v);
total += 1;
}
for (n, v) in shard.global_aliases {
executor.set_global_alias(n, v);
total += 1;
}
for (n, v) in shard.suffix_aliases {
executor.set_suffix_alias(n, v);
total += 1;
}
for (n, v) in shard.env_exports {
std::env::set_var(&n, &v);
executor.set_scalar(n, v);
total += 1;
}
for (n, v) in shard.params {
executor.set_scalar(n, v);
total += 1;
}
for opt in shard.setopts {
crate::ported::options::opt_state_set(&opt, true);
total += 1;
}
for opt in shard.unsetopts {
crate::ported::options::opt_state_set(&opt, false);
total += 1;
}
if !shard.path.is_empty() {
let joined = shard.path.join(":");
std::env::set_var("PATH", &joined);
executor.set_scalar("PATH".to_string(), joined);
total += shard.path.len();
executor.set_array("path".to_string(), shard.path);
}
if !shard.fpath.is_empty() {
let joined = shard.fpath.join(":");
std::env::set_var("FPATH", &joined);
executor.set_scalar("FPATH".to_string(), joined);
total += shard.fpath.len();
executor.fpath = shard.fpath.iter().map(PathBuf::from).collect();
executor.set_array("fpath".to_string(), shard.fpath);
}
for (name, path) in shard.named_dirs {
if let Ok(mut tab) = crate::ported::hashnameddir::nameddirtab().lock() {
tab.insert(name.clone(), crate::ported::zsh_h::nameddir {
node: crate::ported::zsh_h::hashnode {
next: None,
nam: name,
flags: 0,
},
dir: path.clone(),
diff: 0,
});
total += 1;
}
}
let _ = AutoloadFlags::NO_ALIAS;
for name in shard.autoload_functions.keys() {
if let Ok(mut tab) = crate::ported::hashtable::shfunctab_lock().write() {
tab.add(crate::ported::hashtable::shfunc_autoload(name));
}
total += 1;
}
for (pattern, rest) in shard.zstyle {
let mut parts = rest.split_whitespace();
let style = match parts.next() {
Some(s) => s.to_string(),
None => continue,
};
let values: Vec<String> = parts.map(str::to_string).collect();
executor.zstyles.push(zstyle_entry {
pattern,
style,
values,
});
total += 1;
}
{
for (keyseq, value) in shard.bindkeys {
let (keymap, widget) = parse_bindkey_value(&value);
crate::ported::zle::zle_bindings::bindkey(keymap, &keyseq, widget);
total += 1;
}
}
if !shard.compdef.is_empty() {
if executor.compsys_cache.is_none() {
let cache_path = compsys::cache::default_cache_path();
if let Some(parent) = cache_path.parent() {
let _ = std::fs::create_dir_all(parent);
}
match compsys::cache::CompsysCache::open(&cache_path) {
Ok(c) => {
tracing::info!(
path = %cache_path.display(),
"compsys cache lazily created for canonical compdef apply"
);
executor.compsys_cache = Some(c);
}
Err(e) => {
tracing::warn!(error = %e, "compsys cache open failed; compdef rows skipped");
}
}
}
if let Some(cache) = executor.compsys_cache.as_mut() {
for (function, cmds_joined) in shard.compdef {
let mut args: Vec<String> = Vec::with_capacity(8);
args.push(function);
for cmd in cmds_joined.split_whitespace() {
args.push(cmd.to_string());
}
if args.len() < 2 {
continue; }
let _rc = compsys::compdef::compdef_execute(cache, &args);
total += 1;
}
}
}
if let Some(zle_widgets) = shard.extras.get("zle") {
for (name, body) in zle_widgets {
let w = std::sync::Arc::new(
crate::ported::zle::zle_h::widget::user_defined(name, body),
);
crate::ported::zle::zle_thingy::rthingy(name);
crate::ported::zle::zle_thingy::bindwidget(w, name);
total += 1;
}
}
let _ = shard.functions;
let _ = shard.zmodload;
let _ = shard.manpath;
let _ = shard.plugins;
let _ = shard.sourced_files;
let _ = shard.extras;
total
}
fn parse_bindkey_value(value: &str) -> (&str, &str) {
if let Some(rest) = value.strip_prefix('[') {
if let Some(close_idx) = rest.find(']') {
let keymap_str = &rest[..close_idx];
let widget = rest[close_idx + 1..].trim_start();
return (keymap_str, widget);
}
}
("main", value)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bindkey_value_parses_main_keymap_default() {
let (km, w) = parse_bindkey_value("history-search-backward");
assert_eq!(km, "main");
assert_eq!(w, "history-search-backward");
}
#[test]
fn bindkey_value_parses_explicit_keymap() {
let (km, w) = parse_bindkey_value("[viins] backward-delete-char");
assert_eq!(km, "viins");
assert_eq!(w, "backward-delete-char");
}
#[test]
fn bindkey_value_unknown_keymap_falls_back_to_main() {
let (km, w) = parse_bindkey_value("[totally-not-real] do-thing");
assert_eq!(km, "totally-not-real");
assert_eq!(w, "do-thing");
}
#[test]
fn bindkey_value_handles_extra_whitespace_after_close_bracket() {
let (km, w) = parse_bindkey_value("[vicmd] forward-word");
assert_eq!(km, "vicmd");
assert_eq!(w, "forward-word");
}
}