Skip to main content

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                    crate::compsys::ported::compinit::register_autoload_stubs(
44                        crate::compsys::ported::compinit::autoload_stub_names(&bg.result),
45                    );
46                    self.set_assoc("_comps".to_string(), bg.result.comps.into_iter().collect());
47                    self.set_assoc(
48                        "_services".to_string(),
49                        bg.result.services.into_iter().collect(),
50                    );
51                    self.set_assoc(
52                        "_patcomps".to_string(),
53                        bg.result.patcomps.into_iter().collect(),
54                    );
55                    self.compsys_cache = std::cell::OnceCell::from(Some(bg.cache));
56                    tracing::info!(
57                        wall_ms = start.elapsed().as_millis() as u64,
58                        comps,
59                        "compinit: background results merged"
60                    );
61                }
62                Err(std::sync::mpsc::TryRecvError::Empty) => {
63                    // Not ready yet — put the receiver back for next poll
64                    self.compinit_pending = Some((rx, start));
65                }
66                Err(std::sync::mpsc::TryRecvError::Disconnected) => {
67                    tracing::warn!("compinit: background thread died without sending results");
68                }
69            }
70        }
71    }
72    /// Traditional zsh compinit (--zsh-compat mode)
73    /// Uses fpath scanning, .zcompdump files, no SQLite
74    pub(crate) fn compinit_compat(
75        &mut self,
76        quiet: bool,
77        no_dump: bool,
78        dump_file: Option<String>,
79        use_cache: bool,
80    ) -> i32 {
81        let zdotdir = self
82            .scalar("ZDOTDIR")
83            .or_else(|| std::env::var("ZDOTDIR").ok())
84            .unwrap_or_else(|| std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()));
85
86        let dump_path = dump_file
87            .map(PathBuf::from)
88            .unwrap_or_else(|| PathBuf::from(&zdotdir).join(".zcompdump"));
89
90        // -C: Try to use existing .zcompdump if valid
91        if use_cache
92            && dump_path.exists()
93            && crate::compsys::check_dump(&dump_path, &self.fpath, "zshrs-0.1.0")
94        {
95            // Valid dump - source it to load _comps
96            // For now, just rescan (proper impl would source the dump file)
97            if !quiet {
98                tracing::info!("compinit: .zcompdump valid, rescanning for compat");
99            }
100        }
101
102        // Full fpath scan (traditional zsh algorithm)
103        let result = crate::compsys::compinit(&self.fpath);
104
105        if !quiet {
106            tracing::info!(
107                functions = result.files_scanned,
108                comps = result.comps.len(),
109                dirs = result.dirs_scanned,
110                ms = result.scan_time_ms,
111                "compinit: fpath scan complete"
112            );
113        }
114
115        // Write .zcompdump unless -D
116        if !no_dump {
117            let _ = crate::compsys::compdump(&result, &dump_path, "zshrs-0.1.0");
118        }
119
120        // compinit sh:337/sh:541 — `compdef -na` autoloads every completer it
121        // registers, so `${(k)functions}` holds a stub for each one (see
122        // `register_autoload_stubs`).
123        crate::compsys::ported::compinit::register_autoload_stubs(
124            crate::compsys::ported::compinit::autoload_stub_names(&result),
125        );
126
127        // Set up _comps associative array
128        self.set_assoc(
129            "_comps".to_string(),
130            result.comps.clone().into_iter().collect(),
131        );
132        self.set_assoc(
133            "_services".to_string(),
134            result.services.clone().into_iter().collect(),
135        );
136        self.set_assoc(
137            "_patcomps".to_string(),
138            result.patcomps.clone().into_iter().collect(),
139        );
140
141        // `#compdef -k`/`-K` header bindings (^X? _complete_debug,
142        // ^Xh _complete_help, …) — the in-shell half of the scan.
143        crate::compsys::ported::compinit::apply_keybindings(&result);
144
145        // No SQLite cache in compat mode
146        self.compsys_cache = std::cell::OnceCell::from(None);
147
148        0
149    }
150}
151// END moved-from-exec-rs