#![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};
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.aliases.insert(n, v);
total += 1;
}
for (n, v) in shard.global_aliases {
executor.global_aliases.insert(n, v);
total += 1;
}
for (n, v) in shard.suffix_aliases {
executor.suffix_aliases.insert(n, v);
total += 1;
}
for (n, v) in shard.env_exports {
std::env::set_var(&n, &v);
executor.variables.insert(n, v);
total += 1;
}
for (n, v) in shard.params {
executor.variables.insert(n, v);
total += 1;
}
for opt in shard.setopts {
executor.options.insert(opt, true);
total += 1;
}
for opt in shard.unsetopts {
executor.options.insert(opt, false);
total += 1;
}
if !shard.path.is_empty() {
let joined = shard.path.join(":");
std::env::set_var("PATH", &joined);
executor.variables.insert("PATH".to_string(), joined);
total += shard.path.len();
executor.arrays.insert("path".to_string(), shard.path);
}
if !shard.fpath.is_empty() {
let joined = shard.fpath.join(":");
std::env::set_var("FPATH", &joined);
executor.variables.insert("FPATH".to_string(), joined);
total += shard.fpath.len();
executor.fpath = shard.fpath.iter().map(PathBuf::from).collect();
executor.arrays.insert("fpath".to_string(), shard.fpath);
}
for (name, path) in shard.named_dirs {
executor.named_dirs.insert(name, PathBuf::from(path));
total += 1;
}
let auto_flags = AutoloadFlags::NO_ALIAS | AutoloadFlags::ZSH_STYLE;
for name in shard.autoload_functions.keys() {
executor
.autoload_pending
.insert(name.clone(), auto_flags);
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 {
pattern,
style,
values,
});
total += 1;
}
{
use crate::zle::{zle, KeymapName};
let mut zle_state = zle();
for (keyseq, value) in shard.bindkeys {
let (keymap, widget) = parse_bindkey_value(&value);
zle_state.bind_key(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") {
use crate::zle::zle;
let mut zle_state = zle();
for (name, body) in zle_widgets {
zle_state.user_widgets.insert(name.clone(), body.clone());
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) -> (crate::zle::KeymapName, &str) {
use crate::zle::KeymapName;
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();
let keymap = KeymapName::from_str(keymap_str).unwrap_or(KeymapName::Main);
return (keymap, widget);
}
}
(KeymapName::Main, value)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::zle::KeymapName;
#[test]
fn bindkey_value_parses_main_keymap_default() {
let (km, w) = parse_bindkey_value("history-search-backward");
assert_eq!(km, KeymapName::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, KeymapName::ViInsert);
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, KeymapName::Main);
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, KeymapName::ViCommand);
assert_eq!(w, "forward-word");
}
}