zsh/extensions/compinit_bg.rs
1//! Background compinit pre-warm — extension; no zsh C counterpart.
2use crate::compsys::cache::CompsysCache;
3use crate::compsys::CompInitResult;
4#[allow(unused_imports)]
5use crate::ported::vm_helper::ShellExecutor;
6#[allow(unused_imports)]
7use std::{collections::HashMap, env, path::PathBuf};
8
9/// Result from background compinit thread.
10/// Outcome of background `compinit` autoload.
11/// zshrs-original — Src/Modules/complete.c blocks on `compinit`
12/// inline. The Rust port runs it on the worker pool.
13pub struct CompInitBgResult {
14 /// `result` field.
15 pub result: CompInitResult,
16 /// `cache` field.
17 pub cache: CompsysCache,
18}
19
20// ===========================================================
21// Methods moved verbatim from src/ported/vm_helper because their
22// C counterpart's source file maps 1:1 to this Rust module.
23// Phase: drift
24// ===========================================================
25
26// BEGIN moved-from-exec-rs
27impl crate::ported::vm_helper::ShellExecutor {
28 /// Non-blocking drain of background compinit results.
29 /// Call this before any completion lookup (prompt, tab-complete, etc.).
30 /// If the background thread hasn't finished yet, this is a no-op.
31 pub fn drain_compinit_bg(&mut self) {
32 tracing::debug!(target: "compsys_args", pending = self.compinit_pending.is_some(), "drain_compinit_bg ENTER");
33 if let Some((rx, start)) = self.compinit_pending.take() {
34 match rx.try_recv() {
35 Ok(bg) => {
36 let comps = bg.result.comps.len();
37 // `#compdef -k`/`-K` header bindings — must run on the
38 // shell thread (dispatches zle -C + bindkey).
39 crate::compsys::ported::compinit::apply_keybindings(&bg.result);
40 // compinit sh:337/sh:541 — `compdef -na` autoloads every
41 // completer it registers, so `${(k)functions}` holds a stub
42 // for each one (see `register_autoload_stubs`).
43 let mut stubs = crate::compsys::ported::compinit::register_autoload_stubs(
44 crate::compsys::ported::compinit::autoload_stub_names(&bg.result),
45 );
46 // `autoload_stub_names` reads `result.files`, which only a
47 // fresh `$fpath` scan fills: `load_from_cache`
48 // (compinit.rs) rebuilds `comps`/`patcomps`/`postpatcomps`
49 // out of SQLite and leaves `files` empty. The background
50 // thread returns whichever of the two it ended up taking,
51 // so on every cache-hit start the call above registered
52 // ZERO stubs and `${(k)functions}` came back holding no
53 // `_*` name at all -- measured `${#${(M)${(k)functions}:#_*}}`
54 // = 0 against a 1822-entry `$_comps`. `zle -C`'s entry
55 // point `_main_complete` was one of the missing names, so
56 // every completion widget compinit binds called an
57 // undefined function and inserted nothing. The `autoloads`
58 // table carries the same names the scan would have
59 // produced, so read it whenever the file list came back in
60 // the empty cache shape.
61 if bg.result.files.is_empty() {
62 if let Ok(names) = bg.cache.list_autoload_names() {
63 stubs += crate::compsys::ported::compinit::register_autoload_stubs(
64 &names,
65 );
66 }
67 }
68 tracing::info!(stubs, "compinit: autoload stubs from background result");
69 self.set_assoc("_comps".to_string(), bg.result.comps.into_iter().collect());
70 self.set_assoc(
71 "_services".to_string(),
72 bg.result.services.into_iter().collect(),
73 );
74 self.set_assoc(
75 "_patcomps".to_string(),
76 bg.result.patcomps.into_iter().collect(),
77 );
78 self.compsys_cache = std::cell::OnceCell::from(Some(bg.cache));
79 tracing::info!(
80 wall_ms = start.elapsed().as_millis() as u64,
81 comps,
82 "compinit: background results merged"
83 );
84 }
85 Err(std::sync::mpsc::TryRecvError::Empty) => {
86 // Not ready yet — put the receiver back for next poll
87 self.compinit_pending = Some((rx, start));
88 }
89 Err(std::sync::mpsc::TryRecvError::Disconnected) => {
90 tracing::warn!("compinit: background thread died without sending results");
91 }
92 }
93 }
94 }
95 /// Traditional zsh compinit (--zsh-compat mode)
96 /// Uses fpath scanning, .zcompdump files, no SQLite
97 pub(crate) fn compinit_compat(
98 &mut self,
99 quiet: bool,
100 no_dump: bool,
101 dump_file: Option<String>,
102 use_cache: bool,
103 ) -> i32 {
104 let zdotdir = self
105 .scalar("ZDOTDIR")
106 .or_else(|| std::env::var("ZDOTDIR").ok())
107 .unwrap_or_else(|| std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()));
108
109 let dump_path = dump_file
110 .map(PathBuf::from)
111 .unwrap_or_else(|| PathBuf::from(&zdotdir).join(".zcompdump"));
112
113 // -C: Try to use existing .zcompdump if valid
114 if use_cache
115 && dump_path.exists()
116 && crate::compsys::check_dump(&dump_path, &self.fpath, "zshrs-0.1.0")
117 {
118 // Valid dump - source it to load _comps
119 // For now, just rescan (proper impl would source the dump file)
120 if !quiet {
121 tracing::info!("compinit: .zcompdump valid, rescanning for compat");
122 }
123 }
124
125 // Full fpath scan (traditional zsh algorithm)
126 let result = crate::compsys::compinit(&self.fpath);
127
128 if !quiet {
129 tracing::info!(
130 functions = result.files_scanned,
131 comps = result.comps.len(),
132 dirs = result.dirs_scanned,
133 ms = result.scan_time_ms,
134 "compinit: fpath scan complete"
135 );
136 }
137
138 // Write .zcompdump unless -D
139 if !no_dump {
140 let _ = crate::compsys::compdump(&result, &dump_path, "zshrs-0.1.0");
141 }
142
143 // compinit sh:337/sh:541 — `compdef -na` autoloads every completer it
144 // registers, so `${(k)functions}` holds a stub for each one (see
145 // `register_autoload_stubs`).
146 crate::compsys::ported::compinit::register_autoload_stubs(
147 crate::compsys::ported::compinit::autoload_stub_names(&result),
148 );
149
150 // Set up _comps associative array
151 self.set_assoc(
152 "_comps".to_string(),
153 result.comps.clone().into_iter().collect(),
154 );
155 self.set_assoc(
156 "_services".to_string(),
157 result.services.clone().into_iter().collect(),
158 );
159 self.set_assoc(
160 "_patcomps".to_string(),
161 result.patcomps.clone().into_iter().collect(),
162 );
163
164 // `#compdef -k`/`-K` header bindings (^X? _complete_debug,
165 // ^Xh _complete_help, …) — the in-shell half of the scan.
166 crate::compsys::ported::compinit::apply_keybindings(&result);
167
168 // No SQLite cache in compat mode
169 self.compsys_cache = std::cell::OnceCell::from(None);
170
171 0
172 }
173}
174// END moved-from-exec-rs