Skip to main content

nu_protocol/engine/
engine_state.rs

1use crate::{
2    BlockId, Config, DeclId, FileId, GetSpan, Handlers, HistoryConfig, JobId, Module, ModuleId,
3    OverlayId, ShellError, SignalAction, Signals, Signature, Span, SpanId, Type, Value, VarId,
4    VirtualPathId,
5    ast::{Block, Expr},
6    debugger::{Debugger, NoopDebugger},
7    engine::{
8        CachedFile, Command, DEFAULT_OVERLAY_NAME, EnvName, EnvVars, OverlayFrame, PromptState,
9        ScopeFrame, Stack, StateDelta, Variable, VisibilityStack,
10        description::{Doccomments, build_desc},
11    },
12    eval_const::create_nu_constant,
13    report_error::ReportLog,
14    shell_error::{generic::GenericError, io::IoError},
15};
16use fancy_regex::Regex;
17use lru::LruCache;
18use nu_config::NushellConfigDirs;
19use nu_path::AbsolutePathBuf;
20use std::{
21    collections::{HashMap, HashSet},
22    num::NonZeroUsize,
23    path::PathBuf,
24    sync::{
25        Arc, Mutex, MutexGuard, PoisonError,
26        atomic::{AtomicBool, AtomicU32, Ordering},
27        mpsc::Sender,
28        mpsc::channel,
29    },
30};
31
32type PoisonDebuggerError<'a> = PoisonError<MutexGuard<'a, Box<dyn Debugger>>>;
33
34#[cfg(feature = "plugin")]
35use crate::{PluginRegistryFile, PluginRegistryItem, RegisteredPlugin};
36
37use super::{CurrentJob, Jobs, Mail, Mailbox, ThreadJob};
38
39/// Configure whether the current working directory may be updated when [`EngineState::merge_env`]
40/// is called.
41///
42/// During testing, this is causing issues, so this may disable it.
43pub static UPDATE_CWD: AtomicBool = AtomicBool::new(true);
44
45#[derive(Clone, Debug)]
46pub enum VirtualPath {
47    File(FileId),
48    Dir(Vec<VirtualPathId>),
49}
50
51#[derive(Debug, Default)]
52pub struct ReplState {
53    pub buffer: String,
54    // A byte position, as `EditCommand::MoveToPosition` is also a byte position
55    pub cursor_pos: usize,
56    /// Immediately accept the buffer on the next loop.
57    pub accept: bool,
58}
59
60#[derive(Debug)]
61pub struct IsDebugging(AtomicBool);
62
63impl IsDebugging {
64    pub fn new(val: bool) -> Self {
65        IsDebugging(AtomicBool::new(val))
66    }
67}
68
69impl Clone for IsDebugging {
70    fn clone(&self) -> Self {
71        IsDebugging(AtomicBool::new(self.0.load(Ordering::Relaxed)))
72    }
73}
74
75/// The core global engine state. This includes all global definitions as well as any global state that
76/// will persist for the whole session.
77///
78/// Declarations, variables, blocks, and other forms of data are held in the global state and referenced
79/// elsewhere using their IDs. These IDs are simply their index into the global state. This allows us to
80/// more easily handle creating blocks, binding variables and callsites, and more, because each of these
81/// will refer to the corresponding IDs rather than their definitions directly. At runtime, this means
82/// less copying and smaller structures.
83///
84/// Many of the larger objects in this structure are stored within `Arc` to decrease the cost of
85/// cloning `EngineState`. While `Arc`s are generally immutable, they can be modified using
86/// `Arc::make_mut`, which automatically clones to a new allocation if there are other copies of
87/// the `Arc` already in use, but will let us modify the `Arc` directly if we have the only
88/// reference to it.
89///
90/// Note that the runtime stack is not part of this global state. Runtime stacks are handled differently,
91/// but they also rely on using IDs rather than full definitions.
92#[derive(Clone, derive_more::Debug)]
93pub struct EngineState {
94    files: Vec<CachedFile>,
95    pub(super) virtual_paths: Vec<(String, VirtualPath)>,
96    vars: Vec<Variable>,
97    #[debug("{:?}", decls.iter().map(|c| c.name()).collect::<Vec<_>>())]
98    decls: Arc<Vec<Box<dyn Command + 'static>>>,
99    // The Vec is wrapped in Arc so that if we don't need to modify the list, we can just clone
100    // the reference and not have to clone each individual Arc inside. These lists can be
101    // especially long, so it helps
102    #[debug("{:?}", blocks.iter().map(|b| &b.signature.name))]
103    pub(super) blocks: Arc<Vec<Arc<Block>>>,
104    #[debug("{:?}", modules.iter().map(|m| String::from_utf8_lossy(&m.name)))]
105    pub(super) modules: Arc<Vec<Arc<Module>>>,
106    pub spans: Vec<Span>,
107    doccomments: Doccomments,
108    pub scope: ScopeFrame,
109    signals: Signals,
110    pub signal_handlers: Option<Handlers>,
111    pub env_vars: Arc<EnvVars>,
112    pub previous_env_vars: Arc<HashMap<EnvName, Value>>,
113    /// Live config. Replace it with [`Self::set_config`] so [`Self::config_epoch`] stays in sync.
114    pub config: Arc<Config>,
115    /// Incremented when [`Self::config`] is replaced (`set_config`, `merge_env`).
116    config_epoch: u64,
117    pub pipeline_externals_state: Arc<(AtomicU32, AtomicU32)>,
118    pub repl_state: Arc<Mutex<ReplState>>,
119    /// Shared source of truth for the interactive prompt's rendered content. The
120    /// REPL fills it each cycle from `$env.PROMPT_COMMAND` and friends; a
121    /// background job's `commandline set-prompt` overwrites individual segments
122    /// and triggers an in-place repaint. Shared with every job that clones this
123    /// engine state.
124    pub prompt_state: Arc<PromptState>,
125    pub table_decl_id: Option<DeclId>,
126    #[cfg(feature = "plugin")]
127    pub plugin_path: Option<PathBuf>,
128    #[cfg(feature = "plugin")]
129    #[debug("{:?}", plugins.iter().map(|rp| rp.identity().name()).collect::<Vec<_>>())]
130    plugins: Vec<Arc<dyn RegisteredPlugin>>,
131    /// Resolved configuration directories and file paths.
132    ///
133    /// Populated once at startup by `nu_config::resolve_paths()` in `main.rs`.
134    /// All downstream code (config-file loading, `$nu` constant generation,
135    /// history backend, etc.) reads from this struct.
136    pub config_dirs: NushellConfigDirs,
137
138    pub history_enabled: bool,
139    pub history_session_id: i64,
140    /// Whether the startup-only `$env.config.history.*` options are locked from further
141    /// changes (currently `path`, `max_size`, `file_format`, `isolation`).
142    ///
143    /// Set to `true` once the REPL has finished initializing reedline's history backend.
144    /// After that point, changing any of these options would have no effect on the live
145    /// history, so attempts to mutate them are rejected with an error instead of being
146    /// silently ignored.
147    pub history_locked_after_startup: bool,
148    // Path to the file Nushell is currently evaluating, or None if we're in an interactive session.
149    pub file: Option<PathBuf>,
150    pub regex_cache: Arc<Mutex<LruCache<String, Regex>>>,
151    pub is_interactive: bool,
152    /// When true with [`Self::is_interactive`], REPL user-line evaluation may store `$ans`.
153    ///
154    /// Set only around true REPL command evaluation (`do_run_cmd`), not config/env/banner
155    /// startup, so startup scripts do not overwrite interactive last-result.
156    pub capture_repl_last_result: bool,
157    pub is_login: bool,
158    pub is_lsp: bool,
159    pub is_mcp: bool,
160    /// Running as the Debug Adapter Protocol server (`nu --dap`). Like
161    /// `is_lsp`/`is_mcp`, this means stdout is a protocol stream, so anything
162    /// that would print to it must go to stderr instead.
163    pub is_dap: bool,
164    startup_time: i64,
165    is_debugging: IsDebugging,
166    pub debugger: Arc<Mutex<Box<dyn Debugger>>>,
167    pub report_log: Arc<Mutex<ReportLog>>,
168
169    pub jobs: Arc<Mutex<Jobs>>,
170
171    // The job being executed with this engine state, or None if main thread
172    pub current_job: CurrentJob,
173
174    pub root_job_sender: Sender<Mail>,
175
176    // When there are background jobs running, the interactive behavior of `exit` changes depending on
177    // the value of this flag:
178    // - if this is false, then a warning about running jobs is shown and `exit` enables this flag
179    // - if this is true, then `exit` will `std::process::exit`
180    //
181    // This ensures that running exit twice will terminate the program correctly
182    pub exit_warning_given: Arc<AtomicBool>,
183}
184
185// The max number of compiled regexes to keep around in a LRU cache, arbitrarily chosen
186const REGEX_CACHE_SIZE: usize = 100; // must be nonzero, otherwise will panic
187
188pub const NU_VARIABLE_ID: VarId = VarId::new(0);
189pub const IN_VARIABLE_ID: VarId = VarId::new(1);
190pub const ENV_VARIABLE_ID: VarId = VarId::new(2);
191/// Interactive last-result special variable.
192///
193/// The user-facing name is [`LAST_RESULT_VAR_NAME`] (e.g. `$ans`). Change that constant
194/// if the public name should differ; keep this ID stable.
195pub const LAST_VARIABLE_ID: VarId = VarId::new(3);
196/// Identifier for the last-result special variable **without** the `$` sigil.
197///
198/// Change this single constant to rename the binding site-wide (e.g. `"ans"` → `$ans`).
199/// The name is reserved (cannot be rebound with `let` / `mut` / `const`). Not user-configurable.
200///
201/// With a positive `max_last_result_size`, `$ans` is `{ last, exit_code, duration, command }`.
202/// With size `0`, `$ans` is `{ exit_code, duration, command }` (`last` omitted). `command` is
203/// the exact last REPL source (same buffer reedline stores in history). Resolved from
204/// the stack's last-result slot, not from captured locals — keep capture-discovery
205/// `>` checks in sync with this being the last special var id.
206pub const LAST_RESULT_VAR_NAME: &str = "ans";
207// NOTE: If you add more specials after LAST_VARIABLE_ID, update capture discovery
208// (`var_id > LAST_VARIABLE_ID`) and any other special-id thresholds.
209
210// The first span is unknown span
211pub const UNKNOWN_SPAN_ID: SpanId = SpanId::new(0);
212
213impl EngineState {
214    pub fn new() -> Self {
215        let (send, recv) = channel::<Mail>();
216
217        Self {
218            files: vec![],
219            virtual_paths: vec![],
220            vars: vec![
221                Variable::new(Span::new(0, 0), Type::Any, false),
222                Variable::new(Span::new(0, 0), Type::Any, false),
223                Variable::new(Span::new(0, 0), Type::Any, false),
224                Variable::new(Span::new(0, 0), Type::Any, false),
225                Variable::new(Span::new(0, 0), Type::Any, false),
226            ],
227            decls: Arc::new(vec![]),
228            blocks: Arc::new(vec![]),
229            modules: Arc::new(vec![Arc::new(Module::new(
230                DEFAULT_OVERLAY_NAME.as_bytes().to_vec(),
231            ))]),
232            spans: vec![Span::unknown()],
233            doccomments: Doccomments::new(),
234            // make sure we have some default overlay:
235            scope: ScopeFrame::with_empty_overlay(
236                DEFAULT_OVERLAY_NAME.as_bytes().to_vec(),
237                ModuleId::new(0),
238                false,
239            ),
240            signal_handlers: None,
241            signals: Signals::empty(),
242            env_vars: Arc::new(
243                [(DEFAULT_OVERLAY_NAME.to_string(), HashMap::new())]
244                    .into_iter()
245                    .collect(),
246            ),
247            previous_env_vars: Arc::new(HashMap::new()),
248            config: Arc::new(Config::default()),
249            config_epoch: 0,
250            pipeline_externals_state: Arc::new((AtomicU32::new(0), AtomicU32::new(0))),
251            repl_state: Arc::new(Mutex::new(ReplState {
252                buffer: "".to_string(),
253                cursor_pos: 0,
254                accept: false,
255            })),
256            prompt_state: Arc::new(PromptState::new()),
257            table_decl_id: None,
258            #[cfg(feature = "plugin")]
259            plugin_path: None,
260            #[cfg(feature = "plugin")]
261            plugins: vec![],
262            config_dirs: NushellConfigDirs::empty(),
263            history_enabled: true,
264            history_session_id: 0,
265            history_locked_after_startup: false,
266            file: None,
267            regex_cache: Arc::new(Mutex::new(LruCache::new(
268                NonZeroUsize::new(REGEX_CACHE_SIZE).expect("tried to create cache of size zero"),
269            ))),
270            is_interactive: false,
271            capture_repl_last_result: false,
272            is_login: false,
273            is_lsp: false,
274            is_mcp: false,
275            is_dap: false,
276            startup_time: -1,
277            is_debugging: IsDebugging::new(false),
278            debugger: Arc::new(Mutex::new(Box::new(NoopDebugger))),
279            report_log: Arc::default(),
280            jobs: Arc::new(Mutex::new(Jobs::default())),
281            current_job: CurrentJob {
282                id: JobId::new(0),
283                background_thread_job: None,
284                mailbox: Arc::new(Mutex::new(Mailbox::new(recv))),
285            },
286            root_job_sender: send,
287            exit_warning_given: Arc::new(AtomicBool::new(false)),
288        }
289    }
290
291    pub fn signals(&self) -> &Signals {
292        &self.signals
293    }
294
295    /// Return a compiled regex, reusing the process-wide LRU cache when possible.
296    ///
297    /// On lock contention (or a poisoned mutex), compiles without touching the cache.
298    pub fn get_cached_regex(&self, pattern: &str) -> Result<Regex, fancy_regex::Error> {
299        match self.regex_cache.try_lock() {
300            Ok(mut cache) => cache
301                .try_get_or_insert_ref(pattern, || Regex::new(pattern))
302                .cloned(),
303            Err(_) => Regex::new(pattern),
304        }
305    }
306
307    /// Compile `pattern` via [`Self::get_cached_regex`], mapping failures to
308    /// [`ShellError::InvalidValue`] at `span`.
309    pub fn compile_regex(&self, pattern: &str, span: Span) -> Result<Regex, ShellError> {
310        self.get_cached_regex(pattern)
311            .map_err(|err| invalid_regex_value(pattern, err, span))
312    }
313
314    pub fn reset_signals(&mut self) {
315        self.signals.reset();
316        if let Some(ref handlers) = self.signal_handlers {
317            handlers.run(SignalAction::Reset);
318        }
319    }
320
321    pub fn set_signals(&mut self, signals: Signals) {
322        self.signals = signals;
323    }
324
325    /// Merges a `StateDelta` onto the current state. These deltas come from a system, like the parser, that
326    /// creates a new set of definitions and visible symbols in the current scope. We make this transactional
327    /// as there are times when we want to run the parser and immediately throw away the results (namely:
328    /// syntax highlighting and completions).
329    ///
330    /// When we want to preserve what the parser has created, we can take its output (the `StateDelta`) and
331    /// use this function to merge it into the global state.
332    pub fn merge_delta(&mut self, mut delta: StateDelta) -> Result<(), ShellError> {
333        // Take the mutable reference and extend the permanent state from the working set
334        self.files.extend(delta.files);
335        self.virtual_paths.extend(delta.virtual_paths);
336        self.vars.extend(delta.vars);
337        self.spans.extend(delta.spans);
338        self.doccomments.merge_with(delta.doccomments);
339
340        // Avoid potentially cloning the Arcs if we aren't adding anything
341        if !delta.decls.is_empty() {
342            Arc::make_mut(&mut self.decls).extend(delta.decls);
343        }
344        if !delta.blocks.is_empty() {
345            Arc::make_mut(&mut self.blocks).extend(delta.blocks);
346        }
347        if !delta.modules.is_empty() {
348            Arc::make_mut(&mut self.modules).extend(delta.modules);
349        }
350
351        let first = delta.scope.remove(0);
352
353        for (delta_name, delta_overlay) in first.clone().overlays {
354            if let Some((_, existing_overlay)) = self
355                .scope
356                .overlays
357                .iter_mut()
358                .find(|(name, _)| name == &delta_name)
359            {
360                // Updating existing overlay
361                for item in delta_overlay.decls.into_iter() {
362                    existing_overlay.decls.insert(item.0, item.1);
363                }
364                for item in delta_overlay.vars.into_iter() {
365                    existing_overlay.insert_variable(item.0, item.1);
366                }
367                for item in delta_overlay.modules.into_iter() {
368                    existing_overlay.modules.insert(item.0, item.1);
369                }
370
371                existing_overlay
372                    .visibility
373                    .merge_with(delta_overlay.visibility);
374            } else {
375                // New overlay was added to the delta
376                self.scope.overlays.push((delta_name, delta_overlay));
377            }
378        }
379
380        let mut activated_ids = self.translate_overlay_ids(&first);
381
382        let mut removed_ids = vec![];
383
384        for name in &first.removed_overlays {
385            if let Some(overlay_id) = self.find_overlay(name) {
386                removed_ids.push(overlay_id);
387            }
388        }
389
390        // Remove overlays removed in delta
391        self.scope
392            .active_overlays
393            .retain(|id| !removed_ids.contains(id));
394
395        // Move overlays activated in the delta to be first
396        self.scope
397            .active_overlays
398            .retain(|id| !activated_ids.contains(id));
399        self.scope.active_overlays.append(&mut activated_ids);
400
401        #[cfg(feature = "plugin")]
402        if !delta.plugins.is_empty() {
403            for plugin in std::mem::take(&mut delta.plugins) {
404                // Connect plugins to the signal handlers
405                if let Some(handlers) = &self.signal_handlers {
406                    plugin.clone().configure_signal_handler(handlers)?;
407                }
408
409                // Replace plugins that overlap in identity.
410                if let Some(existing) = self
411                    .plugins
412                    .iter_mut()
413                    .find(|p| p.identity().name() == plugin.identity().name())
414                {
415                    // Stop the existing plugin, so that the new plugin definitely takes over
416                    existing.stop()?;
417                    *existing = plugin;
418                } else {
419                    self.plugins.push(plugin);
420                }
421            }
422        }
423
424        #[cfg(feature = "plugin")]
425        if !delta.plugin_registry_items.is_empty() {
426            // Update the plugin file with the new signatures.
427            if self.plugin_path.is_some() {
428                self.update_plugin_file(std::mem::take(&mut delta.plugin_registry_items))?;
429            }
430        }
431
432        Ok(())
433    }
434
435    /// Merge the environment from the runtime Stack into the engine state
436    pub fn merge_env(&mut self, stack: &mut Stack) -> Result<(), ShellError> {
437        for mut scope in stack.env_vars.drain(..) {
438            for (overlay_name, mut env) in Arc::make_mut(&mut scope).drain() {
439                if let Some(env_vars) = Arc::make_mut(&mut self.env_vars).get_mut(&overlay_name) {
440                    // Updating existing overlay
441                    env_vars.extend(env.drain());
442                } else {
443                    // Pushing a new overlay
444                    Arc::make_mut(&mut self.env_vars).insert(overlay_name, env);
445                }
446            }
447        }
448
449        if UPDATE_CWD.load(Ordering::Relaxed) {
450            let cwd = self.cwd(Some(stack))?;
451            std::env::set_current_dir(cwd)
452                .map_err(|err| IoError::new_internal(err, "Could not set current dir"))?;
453        }
454
455        if let Some(config) = stack.config.take() {
456            // If config was updated in the stack, replace it.
457            self.replace_config(config);
458
459            // Make plugin GC config changes take effect immediately.
460            #[cfg(feature = "plugin")]
461            self.update_plugin_gc_configs(&self.config.plugin_gc);
462        }
463
464        Ok(())
465    }
466
467    /// Clean up unused variables from a Stack to prevent memory leaks.
468    /// This removes variables that are no longer referenced by any overlay or alias.
469    pub fn cleanup_stack_variables(&mut self, stack: &mut Stack) {
470        let mut shadowed_vars = HashSet::new();
471        for (_, frame) in self.scope.overlays.iter_mut() {
472            shadowed_vars.extend(frame.shadowed_vars.drain(..));
473        }
474
475        if shadowed_vars.is_empty() {
476            return;
477        }
478
479        // Collect VarIds still referenced by alias definitions so we don't
480        // remove them — doing so would cause a VariableNotFoundAtRuntime error
481        // when the alias is invoked after the variable has been re-bound.
482        let mut alias_var_ids = HashSet::new();
483        for decl in self.decls.iter() {
484            if let Some(alias) = decl.as_alias() {
485                collect_alias_var_ids(&alias.wrapped_call, &mut alias_var_ids);
486            }
487        }
488
489        // Remove variables from stack that are shadowed and not referenced by any alias
490        stack.vars.retain(|(var_id, _)| {
491            !shadowed_vars.contains(var_id) || alias_var_ids.contains(var_id)
492        });
493    }
494
495    pub fn active_overlay_ids<'a, 'b>(
496        &'b self,
497        removed_overlays: &'a [Vec<u8>],
498    ) -> impl DoubleEndedIterator<Item = &'b OverlayId> + 'a
499    where
500        'b: 'a,
501    {
502        self.scope.active_overlays.iter().filter(|id| {
503            !removed_overlays
504                .iter()
505                .any(|name| name == self.get_overlay_name(**id))
506        })
507    }
508
509    pub fn active_overlays<'a, 'b>(
510        &'b self,
511        removed_overlays: &'a [Vec<u8>],
512    ) -> impl DoubleEndedIterator<Item = &'b OverlayFrame> + 'a
513    where
514        'b: 'a,
515    {
516        self.active_overlay_ids(removed_overlays)
517            .map(|id| self.get_overlay(*id))
518    }
519
520    pub fn active_overlay_names<'a, 'b>(
521        &'b self,
522        removed_overlays: &'a [Vec<u8>],
523    ) -> impl DoubleEndedIterator<Item = &'b [u8]> + 'a
524    where
525        'b: 'a,
526    {
527        self.active_overlay_ids(removed_overlays)
528            .map(|id| self.get_overlay_name(*id))
529    }
530
531    /// Translate overlay IDs from other to IDs in self
532    fn translate_overlay_ids(&self, other: &ScopeFrame) -> Vec<OverlayId> {
533        let other_names = other.active_overlays.iter().map(|other_id| {
534            &other
535                .overlays
536                .get(other_id.get())
537                .expect("internal error: missing overlay")
538                .0
539        });
540
541        other_names
542            .map(|other_name| {
543                self.find_overlay(other_name)
544                    .expect("internal error: missing overlay")
545            })
546            .collect()
547    }
548
549    pub fn last_overlay_name(&self, removed_overlays: &[Vec<u8>]) -> &[u8] {
550        self.active_overlay_names(removed_overlays)
551            .last()
552            .expect("internal error: no active overlays")
553    }
554
555    pub fn last_overlay(&self, removed_overlays: &[Vec<u8>]) -> &OverlayFrame {
556        self.active_overlay_ids(removed_overlays)
557            .last()
558            .map(|id| self.get_overlay(*id))
559            .expect("internal error: no active overlays")
560    }
561
562    pub fn get_overlay_name(&self, overlay_id: OverlayId) -> &[u8] {
563        &self
564            .scope
565            .overlays
566            .get(overlay_id.get())
567            .expect("internal error: missing overlay")
568            .0
569    }
570
571    pub fn get_overlay(&self, overlay_id: OverlayId) -> &OverlayFrame {
572        &self
573            .scope
574            .overlays
575            .get(overlay_id.get())
576            .expect("internal error: missing overlay")
577            .1
578    }
579
580    pub fn render_env_vars(&self) -> HashMap<&str, &Value> {
581        let mut result: HashMap<&str, &Value> = HashMap::new();
582
583        for overlay_name in self.active_overlay_names(&[]) {
584            let name = String::from_utf8_lossy(overlay_name);
585            if let Some(env_vars) = self.env_vars.get(name.as_ref()) {
586                result.extend(env_vars.iter().map(|(k, v)| (k.as_str(), v)));
587            }
588        }
589
590        result
591    }
592
593    pub fn add_env_var(&mut self, name: String, val: Value) {
594        let overlay_name = String::from_utf8_lossy(self.last_overlay_name(&[])).to_string();
595
596        if let Some(env_vars) = Arc::make_mut(&mut self.env_vars).get_mut(&overlay_name) {
597            env_vars.insert(EnvName::from(name), val);
598        } else {
599            Arc::make_mut(&mut self.env_vars).insert(
600                overlay_name,
601                [(EnvName::from(name), val)].into_iter().collect(),
602            );
603        }
604    }
605
606    pub fn get_env_var(&self, name: &str) -> Option<&Value> {
607        for overlay_id in self.scope.active_overlays.iter().rev() {
608            let overlay_name = String::from_utf8_lossy(self.get_overlay_name(*overlay_id));
609            if let Some(env_vars) = self.env_vars.get(overlay_name.as_ref())
610                && let Some(val) = env_vars.get(&EnvName::from(name))
611            {
612                return Some(val);
613            }
614        }
615
616        None
617    }
618
619    #[cfg(feature = "plugin")]
620    pub fn plugins(&self) -> &[Arc<dyn RegisteredPlugin>] {
621        &self.plugins
622    }
623
624    #[cfg(feature = "plugin")]
625    fn update_plugin_file(&self, updated_items: Vec<PluginRegistryItem>) -> Result<(), ShellError> {
626        // Updating the signatures plugin file with the added signatures
627        use std::fs::File;
628
629        let plugin_path = self.plugin_path.as_ref().ok_or_else(|| {
630            ShellError::Generic(
631                GenericError::new_internal("Plugin file path not set", "")
632                    .with_help("you may be running nu with --no-config-file"),
633            )
634        })?;
635
636        // Read the current contents of the plugin file if it exists
637        let mut contents = match File::open(plugin_path.as_path()) {
638            Ok(mut plugin_file) => PluginRegistryFile::read_from(&mut plugin_file, None),
639            Err(err) => {
640                if err.kind() == std::io::ErrorKind::NotFound {
641                    Ok(PluginRegistryFile::default())
642                } else {
643                    Err(ShellError::Io(IoError::new_internal_with_path(
644                        err,
645                        "Failed to open plugin file",
646                        PathBuf::from(plugin_path),
647                    )))
648                }
649            }
650        }?;
651
652        // Update the given signatures
653        for item in updated_items {
654            contents.upsert_plugin(item);
655        }
656
657        // Write it to the same path
658        let plugin_file = File::create(plugin_path.as_path()).map_err(|err| {
659            IoError::new_internal_with_path(
660                err,
661                "Failed to write plugin file",
662                PathBuf::from(plugin_path),
663            )
664        })?;
665
666        contents.write_to(plugin_file, None)
667    }
668
669    /// Update plugins with new garbage collection config
670    #[cfg(feature = "plugin")]
671    fn update_plugin_gc_configs(&self, plugin_gc: &crate::PluginGcConfigs) {
672        for plugin in &self.plugins {
673            plugin.set_gc_config(plugin_gc.get(plugin.identity().name()));
674        }
675    }
676
677    pub fn num_files(&self) -> usize {
678        self.files.len()
679    }
680
681    pub fn num_virtual_paths(&self) -> usize {
682        self.virtual_paths.len()
683    }
684
685    pub fn num_vars(&self) -> usize {
686        self.vars.len()
687    }
688
689    pub fn num_decls(&self) -> usize {
690        self.decls.len()
691    }
692
693    pub fn num_blocks(&self) -> usize {
694        self.blocks.len()
695    }
696
697    pub fn num_modules(&self) -> usize {
698        self.modules.len()
699    }
700
701    pub fn num_spans(&self) -> usize {
702        self.spans.len()
703    }
704    pub fn print_vars(&self) {
705        for var in self.vars.iter().enumerate() {
706            println!("var{}: {:?}", var.0, var.1);
707        }
708    }
709
710    pub fn print_decls(&self) {
711        for decl in self.decls.iter().enumerate() {
712            println!("decl{}: {:?}", decl.0, decl.1.signature());
713        }
714    }
715
716    pub fn print_blocks(&self) {
717        for block in self.blocks.iter().enumerate() {
718            println!("block{}: {:?}", block.0, block.1);
719        }
720    }
721
722    pub fn print_contents(&self) {
723        for cached_file in self.files.iter() {
724            let string = String::from_utf8_lossy(&cached_file.content);
725            println!("{string}");
726        }
727    }
728
729    /// Find the [`DeclId`](crate::DeclId) corresponding to a declaration with `name`.
730    ///
731    /// Searches within active overlays, and filtering out overlays in `removed_overlays`.
732    pub fn find_decl(&self, name: &[u8], removed_overlays: &[Vec<u8>]) -> Option<DeclId> {
733        let mut visibility = VisibilityStack::default();
734
735        for overlay_frame in self.active_overlays(removed_overlays).rev() {
736            visibility.push(&overlay_frame.visibility);
737
738            if let Some(decl_id) = overlay_frame.get_decl(name)
739                && visibility.is_decl_id_visible(&decl_id)
740            {
741                return Some(decl_id);
742            }
743        }
744
745        None
746    }
747
748    /// Find the name of the declaration corresponding to `decl_id`.
749    ///
750    /// Searches within active overlays, and filtering out overlays in `removed_overlays`.
751    pub fn find_decl_name(&self, decl_id: DeclId, removed_overlays: &[Vec<u8>]) -> Option<&[u8]> {
752        let mut visibility = VisibilityStack::default();
753
754        for overlay_frame in self.active_overlays(removed_overlays).rev() {
755            visibility.push(&overlay_frame.visibility);
756
757            if visibility.is_decl_id_visible(&decl_id) {
758                for (name, id) in overlay_frame.decls.iter() {
759                    if id == &decl_id {
760                        return Some(name);
761                    }
762                }
763            }
764        }
765
766        None
767    }
768
769    /// Find the [`OverlayId`](crate::OverlayId) corresponding to `name`.
770    ///
771    /// Searches all overlays, not just active overlays. To search only in active overlays, use [`find_active_overlay`](EngineState::find_active_overlay)
772    pub fn find_overlay(&self, name: &[u8]) -> Option<OverlayId> {
773        self.scope.find_overlay(name)
774    }
775
776    /// Find the [`OverlayId`](crate::OverlayId) of the active overlay corresponding to `name`.
777    ///
778    /// Searches only active overlays. To search in all overlays, use [`find_overlay`](EngineState::find_active_overlay)
779    pub fn find_active_overlay(&self, name: &[u8]) -> Option<OverlayId> {
780        self.scope.find_active_overlay(name)
781    }
782
783    /// Find the [`ModuleId`](crate::ModuleId) corresponding to `name`.
784    ///
785    /// Searches within active overlays, and filtering out overlays in `removed_overlays`.
786    pub fn find_module(&self, name: &[u8], removed_overlays: &[Vec<u8>]) -> Option<ModuleId> {
787        for overlay_frame in self.active_overlays(removed_overlays).rev() {
788            if let Some(module_id) = overlay_frame.modules.get(name) {
789                return Some(*module_id);
790            }
791        }
792
793        None
794    }
795
796    pub fn get_module_comments(&self, module_id: ModuleId) -> Option<&[Span]> {
797        self.doccomments.get_module_comments(module_id)
798    }
799
800    #[cfg(feature = "plugin")]
801    pub fn plugin_decls(&self) -> impl Iterator<Item = &Box<dyn Command + 'static>> {
802        let mut unique_plugin_decls = HashMap::new();
803
804        // Make sure there are no duplicate decls: Newer one overwrites the older one
805        for decl in self.decls.iter().filter(|d| d.is_plugin()) {
806            unique_plugin_decls.insert(decl.name(), decl);
807        }
808
809        let mut plugin_decls: Vec<(&str, &Box<dyn Command>)> =
810            unique_plugin_decls.into_iter().collect();
811
812        // Sort the plugins by name so we don't end up with a random plugin file each time
813        plugin_decls.sort_by(|a, b| a.0.cmp(b.0));
814        plugin_decls.into_iter().map(|(_, decl)| decl)
815    }
816
817    pub fn which_module_has_decl(
818        &self,
819        decl_name: &[u8],
820        removed_overlays: &[Vec<u8>],
821    ) -> Option<&[u8]> {
822        for overlay_frame in self.active_overlays(removed_overlays).rev() {
823            for (module_name, module_id) in overlay_frame.modules.iter() {
824                let module = self.get_module(*module_id);
825                if module.has_decl(decl_name) {
826                    return Some(module_name);
827                }
828            }
829        }
830
831        None
832    }
833
834    /// Apply a function to all commands. The function accepts a command name and its DeclId
835    pub fn traverse_commands(&self, mut f: impl FnMut(&[u8], DeclId)) {
836        for overlay_frame in self.active_overlays(&[]).rev() {
837            for (name, decl_id) in &overlay_frame.decls {
838                if overlay_frame.visibility.is_decl_id_visible(decl_id) {
839                    f(name, *decl_id);
840                }
841            }
842        }
843    }
844
845    pub fn get_span_contents(&self, span: Span) -> &[u8] {
846        self.try_get_file_contents(span).unwrap_or(&[0u8; 0])
847    }
848
849    pub fn try_get_file_contents(&self, span: Span) -> Option<&[u8]> {
850        self.files.iter().find_map(|file| {
851            if file.covered_span.contains_span(span) {
852                let start = span.start - file.covered_span.start;
853                let end = span.end - file.covered_span.start;
854                Some(&file.content[start..end])
855            } else {
856                None
857            }
858        })
859    }
860
861    /// If the span's content starts with the given prefix, return two subspans
862    /// corresponding to this prefix, and the rest of the content.
863    pub fn span_match_prefix(&self, span: Span, prefix: &[u8]) -> Option<(Span, Span)> {
864        let contents = self.get_span_contents(span);
865
866        if contents.starts_with(prefix) {
867            span.split_at(prefix.len())
868        } else {
869            None
870        }
871    }
872
873    /// If the span's content ends with the given postfix, return two subspans
874    /// corresponding to the rest of the content, and this postfix.
875    pub fn span_match_postfix(&self, span: Span, prefix: &[u8]) -> Option<(Span, Span)> {
876        let contents = self.get_span_contents(span);
877
878        if contents.ends_with(prefix) {
879            span.split_at(span.len() - prefix.len())
880        } else {
881            None
882        }
883    }
884
885    /// Get the global config from the engine state.
886    ///
887    /// Use [`Stack::get_config()`] instead whenever the `Stack` is available, as it takes into
888    /// account local changes to `$env.config`.
889    pub fn get_config(&self) -> &Arc<Config> {
890        &self.config
891    }
892
893    /// Identity of the current config object. Changes when `$env.config` is rewritten.
894    pub fn config_epoch(&self) -> u64 {
895        self.config_epoch
896    }
897
898    fn replace_config(&mut self, conf: Arc<Config>) {
899        self.config_epoch = self.config_epoch.wrapping_add(1);
900        self.config = conf;
901    }
902
903    pub fn set_config(&mut self, conf: impl Into<Arc<Config>>) {
904        let conf = conf.into();
905
906        #[cfg(feature = "plugin")]
907        if conf.plugin_gc != self.config.plugin_gc {
908            // Make plugin GC config changes take effect immediately.
909            self.update_plugin_gc_configs(&conf.plugin_gc);
910        }
911
912        self.replace_config(conf);
913    }
914
915    /// Fetch the configuration for a plugin
916    ///
917    /// The `plugin` must match the registered name of a plugin.  For `plugin add
918    /// nu_plugin_example` the plugin name to use will be `"example"`
919    pub fn get_plugin_config(&self, plugin: &str) -> Option<&Value> {
920        self.config.plugins.get(plugin)
921    }
922
923    /// Returns the configuration settings for command history or `None` if history is disabled
924    pub fn history_config(&self) -> Option<HistoryConfig> {
925        self.history_enabled.then(|| self.config.history.clone())
926    }
927
928    /// Resolve the history file path using the already-resolved config dirs.
929    ///
930    /// Returns `None` when history is disabled or the history path is set to
931    /// [`HistoryPath::Disabled`].
932    pub fn history_path(&self) -> Option<std::path::PathBuf> {
933        self.history_config()?
934            .file_path(&self.config_dirs.config_home)
935    }
936
937    pub fn get_var(&self, var_id: VarId) -> &Variable {
938        self.vars
939            .get(var_id.get())
940            .expect("internal error: missing variable")
941    }
942
943    pub fn get_constant(&self, var_id: VarId) -> Option<&Value> {
944        let var = self.get_var(var_id);
945        var.const_val.as_ref()
946    }
947
948    pub fn generate_nu_constant(&mut self) {
949        self.vars[NU_VARIABLE_ID.get()].const_val = Some(create_nu_constant(self, Span::unknown()));
950    }
951
952    pub fn get_decl(&self, decl_id: DeclId) -> &dyn Command {
953        self.decls
954            .get(decl_id.get())
955            .expect("internal error: missing declaration")
956            .as_ref()
957    }
958
959    /// Get all commands within scope, sorted by the commands' names
960    pub fn get_decls_sorted(&self, include_hidden: bool) -> Vec<(Vec<u8>, DeclId)> {
961        let mut decls_map = HashMap::new();
962
963        for overlay_frame in self.active_overlays(&[]) {
964            let new_decls = if include_hidden {
965                overlay_frame.decls.clone()
966            } else {
967                overlay_frame
968                    .decls
969                    .clone()
970                    .into_iter()
971                    .filter(|(_, id)| overlay_frame.visibility.is_decl_id_visible(id))
972                    .collect()
973            };
974
975            decls_map.extend(new_decls);
976        }
977
978        let mut decls: Vec<(Vec<u8>, DeclId)> = decls_map.into_iter().collect();
979
980        decls.sort_by(|a, b| a.0.cmp(&b.0));
981        decls
982    }
983
984    pub fn get_signature(&self, decl: &dyn Command) -> Signature {
985        if let Some(block_id) = decl.block_id() {
986            *self.blocks[block_id.get()].signature.clone()
987        } else {
988            decl.signature()
989        }
990    }
991
992    /// Get signatures of all commands within scope with their decl ids.
993    pub fn get_signatures_and_declids(&self, include_hidden: bool) -> Vec<(Signature, DeclId)> {
994        self.get_decls_sorted(include_hidden)
995            .into_iter()
996            .map(|(_, id)| {
997                let decl = self.get_decl(id);
998
999                (self.get_signature(decl).update_from_command(decl), id)
1000            })
1001            .collect()
1002    }
1003
1004    pub fn get_block(&self, block_id: BlockId) -> &Arc<Block> {
1005        self.blocks
1006            .get(block_id.get())
1007            .expect("internal error: missing block")
1008    }
1009
1010    /// Optionally get a block by id, if it exists
1011    ///
1012    /// Prefer to use [`.get_block()`](Self::get_block) in most cases - `BlockId`s that don't exist
1013    /// are normally a compiler error. This only exists to stop plugins from crashing the engine if
1014    /// they send us something invalid.
1015    pub fn try_get_block(&self, block_id: BlockId) -> Option<&Arc<Block>> {
1016        self.blocks.get(block_id.get())
1017    }
1018
1019    pub fn get_module(&self, module_id: ModuleId) -> &Module {
1020        self.modules
1021            .get(module_id.get())
1022            .expect("internal error: missing module")
1023    }
1024
1025    pub fn get_virtual_path(&self, virtual_path_id: VirtualPathId) -> &(String, VirtualPath) {
1026        self.virtual_paths
1027            .get(virtual_path_id.get())
1028            .expect("internal error: missing virtual path")
1029    }
1030
1031    pub fn next_span_start(&self) -> usize {
1032        if let Some(cached_file) = self.files.last() {
1033            cached_file.covered_span.end
1034        } else {
1035            0
1036        }
1037    }
1038
1039    pub fn files(
1040        &self,
1041    ) -> impl DoubleEndedIterator<Item = &CachedFile> + ExactSizeIterator<Item = &CachedFile> {
1042        self.files.iter()
1043    }
1044
1045    pub fn add_file(&mut self, filename: Arc<str>, content: Arc<[u8]>) -> FileId {
1046        let next_span_start = self.next_span_start();
1047        let next_span_end = next_span_start + content.len();
1048
1049        let covered_span = Span::new(next_span_start, next_span_end);
1050
1051        self.files.push(CachedFile {
1052            name: filename,
1053            content,
1054            covered_span,
1055        });
1056
1057        FileId::new(self.num_files() - 1)
1058    }
1059
1060    pub fn build_desc(&self, spans: &[Span]) -> (String, String) {
1061        let comment_lines: Vec<&[u8]> = spans
1062            .iter()
1063            .map(|span| self.get_span_contents(*span))
1064            .collect();
1065        build_desc(&comment_lines)
1066    }
1067
1068    pub fn build_module_desc(&self, module_id: ModuleId) -> Option<(String, String)> {
1069        self.get_module_comments(module_id)
1070            .map(|comment_spans| self.build_desc(comment_spans))
1071    }
1072
1073    /// Returns the current working directory, which is guaranteed to be canonicalized.
1074    ///
1075    /// Returns an empty String if $env.PWD doesn't exist.
1076    #[deprecated(since = "0.92.3", note = "please use `EngineState::cwd()` instead")]
1077    pub fn current_work_dir(&self) -> String {
1078        self.cwd(None)
1079            .map(|path| path.to_string_lossy().to_string())
1080            .unwrap_or_default()
1081    }
1082
1083    /// Returns the current working directory, which is guaranteed to be an
1084    /// absolute path without trailing slashes (unless it's the root path), but
1085    /// might contain symlink components.
1086    ///
1087    /// If `stack` is supplied, also considers modifications to the working
1088    /// directory on the stack that have yet to be merged into the engine state.
1089    pub fn cwd(&self, stack: Option<&Stack>) -> Result<AbsolutePathBuf, ShellError> {
1090        // Helper function to create a simple generic error.
1091        fn error(msg: &str, cwd: impl AsRef<nu_path::Path>) -> ShellError {
1092            ShellError::Generic(
1093                GenericError::new_internal(
1094                    msg.to_string(),
1095                    format!("$env.PWD = {}", cwd.as_ref().display()),
1096                )
1097                .with_help("Use `cd` to reset $env.PWD into a good state"),
1098            )
1099        }
1100
1101        // Retrieve $env.PWD from the stack or the engine state.
1102        let pwd = if let Some(stack) = stack {
1103            stack.get_env_var(self, "PWD")
1104        } else {
1105            self.get_env_var("PWD")
1106        };
1107
1108        let pwd = pwd.ok_or_else(|| error("$env.PWD not found", ""))?;
1109
1110        if let Ok(pwd) = pwd.as_str() {
1111            let path = AbsolutePathBuf::try_from(pwd)
1112                .map_err(|_| error("$env.PWD is not an absolute path", pwd))?;
1113
1114            // Technically, a root path counts as "having trailing slashes", but
1115            // for the purpose of PWD, a root path is acceptable.
1116            if path.parent().is_some() && nu_path::has_trailing_slash(path.as_ref()) {
1117                Err(error("$env.PWD contains trailing slashes", &path))
1118            } else if !path.exists() {
1119                Err(error("$env.PWD points to a non-existent directory", &path))
1120            } else if !path.is_dir() {
1121                Err(error("$env.PWD points to a non-directory", &path))
1122            } else {
1123                Ok(path)
1124            }
1125        } else {
1126            Err(error("$env.PWD is not a string", format!("{pwd:?}")))
1127        }
1128    }
1129
1130    /// Like `EngineState::cwd()`, but returns a String instead of a PathBuf for convenience.
1131    pub fn cwd_as_string(&self, stack: Option<&Stack>) -> Result<String, ShellError> {
1132        let cwd = self.cwd(stack)?;
1133        cwd.into_os_string()
1134            .into_string()
1135            .map_err(|err| ShellError::NonUtf8Custom {
1136                msg: format!("The current working directory is not a valid utf-8 string: {err:?}"),
1137                span: Span::unknown(),
1138            })
1139    }
1140
1141    // TODO: see if we can completely get rid of this
1142    pub fn get_file_contents(&self) -> &[CachedFile] {
1143        &self.files
1144    }
1145
1146    pub fn get_startup_time(&self) -> i64 {
1147        self.startup_time
1148    }
1149
1150    pub fn set_startup_time(&mut self, startup_time: i64) {
1151        self.startup_time = startup_time;
1152    }
1153
1154    pub fn activate_debugger(
1155        &self,
1156        debugger: Box<dyn Debugger>,
1157    ) -> Result<(), PoisonDebuggerError<'_>> {
1158        let mut locked_debugger = self.debugger.lock()?;
1159        *locked_debugger = debugger;
1160        locked_debugger.activate();
1161        self.is_debugging.0.store(true, Ordering::Relaxed);
1162        Ok(())
1163    }
1164
1165    pub fn deactivate_debugger(&self) -> Result<Box<dyn Debugger>, PoisonDebuggerError<'_>> {
1166        let mut locked_debugger = self.debugger.lock()?;
1167        locked_debugger.deactivate();
1168        let ret = std::mem::replace(&mut *locked_debugger, Box::new(NoopDebugger));
1169        self.is_debugging.0.store(false, Ordering::Relaxed);
1170        Ok(ret)
1171    }
1172
1173    pub fn is_debugging(&self) -> bool {
1174        self.is_debugging.0.load(Ordering::Relaxed)
1175    }
1176
1177    pub fn recover_from_panic(&mut self) {
1178        if Mutex::is_poisoned(&self.repl_state) {
1179            self.repl_state = Arc::new(Mutex::new(ReplState {
1180                buffer: "".to_string(),
1181                cursor_pos: 0,
1182                accept: false,
1183            }));
1184        }
1185        if Mutex::is_poisoned(&self.jobs) {
1186            self.jobs = Arc::new(Mutex::new(Jobs::default()));
1187        }
1188        if Mutex::is_poisoned(&self.regex_cache) {
1189            self.regex_cache = Arc::new(Mutex::new(LruCache::new(
1190                NonZeroUsize::new(REGEX_CACHE_SIZE).expect("tried to create cache of size zero"),
1191            )));
1192        }
1193    }
1194
1195    /// Reset mutable per-session state after cloning a shared engine template.
1196    pub fn make_session_state_unique(&mut self) {
1197        let (send, recv) = channel();
1198
1199        self.pipeline_externals_state = Arc::new((AtomicU32::new(0), AtomicU32::new(0)));
1200        self.repl_state = Default::default();
1201        self.report_log = Default::default();
1202        self.prompt_state = Arc::new(PromptState::new());
1203        self.jobs = Default::default();
1204        self.current_job = CurrentJob {
1205            id: JobId::new(0),
1206            background_thread_job: None,
1207            mailbox: Arc::new(Mutex::new(Mailbox::new(recv))),
1208        };
1209        self.root_job_sender = send;
1210        self.exit_warning_given = Default::default();
1211        self.regex_cache = Arc::new(Mutex::new(LruCache::new(
1212            NonZeroUsize::new(REGEX_CACHE_SIZE).expect("tried to create cache of size zero"),
1213        )));
1214        self.is_debugging = IsDebugging::new(false);
1215        self.debugger = Arc::new(Mutex::new(Box::new(NoopDebugger)));
1216    }
1217
1218    /// Add new span and return its ID
1219    pub fn add_span(&mut self, span: Span) -> SpanId {
1220        self.spans.push(span);
1221        SpanId::new(self.num_spans() - 1)
1222    }
1223
1224    /// Find ID of a span (should be avoided if possible)
1225    pub fn find_span_id(&self, span: Span) -> Option<SpanId> {
1226        self.spans
1227            .iter()
1228            .position(|sp| sp == &span)
1229            .map(SpanId::new)
1230    }
1231
1232    // Determines whether the current state is being held by a background job
1233    pub fn is_background_job(&self) -> bool {
1234        self.current_job.background_thread_job.is_some()
1235    }
1236
1237    // Gets the thread job entry
1238    pub fn current_thread_job(&self) -> Option<&ThreadJob> {
1239        self.current_job.background_thread_job.as_ref()
1240    }
1241}
1242
1243/// Collect all `VarId`s referenced by `Expr::Var` nodes in `expr`.
1244/// This is used by [`EngineState::cleanup_stack_variables`] to avoid removing
1245/// variables that are still referenced by alias definitions.
1246fn collect_alias_var_ids(expr: &crate::ast::Expression, var_ids: &mut HashSet<VarId>) {
1247    let mut queue = vec![expr];
1248
1249    while let Some(e) = queue.pop() {
1250        match &e.expr {
1251            Expr::Var(id) => {
1252                var_ids.insert(*id);
1253            }
1254            Expr::Call(call) => {
1255                for arg in &call.arguments {
1256                    if let Some(sub_expr) = arg.expr() {
1257                        queue.push(sub_expr);
1258                    }
1259                }
1260            }
1261            Expr::ExternalCall(head, args) => {
1262                queue.push(head);
1263                for arg in args.iter() {
1264                    queue.push(arg.expr());
1265                }
1266            }
1267            Expr::FullCellPath(fcp) => queue.push(&fcp.head),
1268            _ => {}
1269        }
1270    }
1271}
1272
1273impl GetSpan for &EngineState {
1274    /// Get existing span
1275    fn get_span(&self, span_id: SpanId) -> Span {
1276        *self
1277            .spans
1278            .get(span_id.get())
1279            .expect("internal error: missing span")
1280    }
1281}
1282
1283impl Default for EngineState {
1284    fn default() -> Self {
1285        Self::new()
1286    }
1287}
1288
1289/// Map a fancy-regex compile failure to [`ShellError::InvalidValue`].
1290///
1291/// Shared by [`EngineState::compile_regex`] and call sites that compile with
1292/// options that bypass the LRU cache (for example a custom backtrack limit).
1293pub fn invalid_regex_value(pattern: &str, err: fancy_regex::Error, span: Span) -> ShellError {
1294    ShellError::InvalidValue {
1295        valid: "a valid regular expression".into(),
1296        actual: format!("'{pattern}' ({err})"),
1297        span,
1298    }
1299}
1300
1301#[cfg(test)]
1302mod engine_state_tests {
1303    use crate::engine::StateWorkingSet;
1304    use std::str::{Utf8Error, from_utf8};
1305
1306    use super::*;
1307
1308    #[test]
1309    fn add_file_gives_id() {
1310        let engine_state = EngineState::new();
1311        let mut engine_state = StateWorkingSet::new(&engine_state);
1312        let id = engine_state.add_file("test.nu", &[]);
1313
1314        assert_eq!(id, FileId::new(0));
1315    }
1316
1317    #[test]
1318    fn get_cached_regex_reuses_compiled_patterns() {
1319        let engine_state = EngineState::new();
1320        let pattern = r"[^\w]+";
1321
1322        let first = engine_state
1323            .get_cached_regex(pattern)
1324            .expect("pattern should compile");
1325        assert!(first.is_match("!!!").unwrap_or(false));
1326
1327        {
1328            let cache = engine_state.regex_cache.lock().expect("cache lock");
1329            assert_eq!(cache.len(), 1);
1330            assert!(cache.peek(pattern).is_some());
1331        }
1332
1333        let second = engine_state
1334            .get_cached_regex(pattern)
1335            .expect("pattern should compile from cache");
1336        assert!(second.is_match("!!!").unwrap_or(false));
1337
1338        {
1339            let cache = engine_state.regex_cache.lock().expect("cache lock");
1340            assert_eq!(cache.len(), 1, "second lookup should not grow the cache");
1341        }
1342    }
1343
1344    #[test]
1345    fn get_cached_regex_does_not_store_invalid_patterns() {
1346        let engine_state = EngineState::new();
1347
1348        assert!(engine_state.get_cached_regex("(").is_err());
1349
1350        let cache = engine_state.regex_cache.lock().expect("cache lock");
1351        assert_eq!(cache.len(), 0);
1352    }
1353
1354    #[test]
1355    fn add_file_gives_id_including_parent() {
1356        let mut engine_state = EngineState::new();
1357        let parent_id = engine_state.add_file("test.nu".into(), Arc::new([]));
1358
1359        let mut working_set = StateWorkingSet::new(&engine_state);
1360        let working_set_id = working_set.add_file("child.nu", &[]);
1361
1362        assert_eq!(parent_id, FileId::new(0));
1363        assert_eq!(working_set_id, FileId::new(1));
1364    }
1365
1366    #[test]
1367    fn merge_states() -> Result<(), ShellError> {
1368        let mut engine_state = EngineState::new();
1369        engine_state.add_file("test.nu".into(), Arc::new([]));
1370
1371        let delta = {
1372            let mut working_set = StateWorkingSet::new(&engine_state);
1373            let _ = working_set.add_file("child.nu", &[]);
1374            working_set.render()
1375        };
1376
1377        engine_state.merge_delta(delta)?;
1378
1379        assert_eq!(engine_state.num_files(), 2);
1380        assert_eq!(&*engine_state.files[0].name, "test.nu");
1381        assert_eq!(&*engine_state.files[1].name, "child.nu");
1382
1383        Ok(())
1384    }
1385
1386    #[test]
1387    fn list_variables() -> Result<(), Utf8Error> {
1388        let varname = "something";
1389        let varname_with_sigil = "$".to_owned() + varname;
1390        let engine_state = EngineState::new();
1391        let mut working_set = StateWorkingSet::new(&engine_state);
1392        working_set.add_variable(
1393            varname.as_bytes().into(),
1394            Span { start: 0, end: 1 },
1395            Type::Int,
1396            false,
1397        );
1398        let variables = working_set
1399            .list_variables()
1400            .into_iter()
1401            .map(from_utf8)
1402            .collect::<Result<Vec<&str>, Utf8Error>>()?;
1403        assert_eq!(variables, vec![varname_with_sigil]);
1404        Ok(())
1405    }
1406
1407    #[test]
1408    fn get_plugin_config() {
1409        let mut engine_state = EngineState::new();
1410
1411        assert!(
1412            engine_state.get_plugin_config("example").is_none(),
1413            "Unexpected plugin configuration"
1414        );
1415
1416        let mut plugins = HashMap::new();
1417        plugins.insert("example".into(), Value::string("value", Span::test_data()));
1418
1419        let mut config = Config::clone(engine_state.get_config());
1420        config.plugins = plugins;
1421
1422        engine_state.set_config(config);
1423
1424        assert!(
1425            engine_state.get_plugin_config("example").is_some(),
1426            "Plugin configuration not found"
1427        );
1428    }
1429}
1430
1431#[cfg(test)]
1432mod test_cwd {
1433    //! Here're the test cases we need to cover:
1434    //!
1435    //! `EngineState::cwd()` computes the result from `self.env_vars["PWD"]` and
1436    //! optionally `stack.env_vars["PWD"]`.
1437    //!
1438    //! PWD may be unset in either `env_vars`.
1439    //! PWD should NOT be an empty string.
1440    //! PWD should NOT be a non-string value.
1441    //! PWD should NOT be a relative path.
1442    //! PWD should NOT contain trailing slashes.
1443    //! PWD may point to a directory or a symlink to directory.
1444    //! PWD should NOT point to a file or a symlink to file.
1445    //! PWD should NOT point to non-existent entities in the filesystem.
1446
1447    use crate::{
1448        Value,
1449        engine::{EngineState, Stack},
1450    };
1451    use nu_path::{AbsolutePath, Path, assert_path_eq};
1452    use tempfile::{NamedTempFile, TempDir};
1453
1454    /// Creates a symlink. Works on both Unix and Windows.
1455    #[cfg(any(unix, windows))]
1456    fn symlink(
1457        original: impl AsRef<AbsolutePath>,
1458        link: impl AsRef<AbsolutePath>,
1459    ) -> std::io::Result<()> {
1460        let original = original.as_ref();
1461        let link = link.as_ref();
1462
1463        #[cfg(unix)]
1464        {
1465            std::os::unix::fs::symlink(original, link)
1466        }
1467        #[cfg(windows)]
1468        {
1469            if original.is_dir() {
1470                std::os::windows::fs::symlink_dir(original, link)
1471            } else {
1472                std::os::windows::fs::symlink_file(original, link)
1473            }
1474        }
1475    }
1476
1477    /// Create an engine state initialized with the given PWD.
1478    fn engine_state_with_pwd(path: impl AsRef<Path>) -> EngineState {
1479        let mut engine_state = EngineState::new();
1480        engine_state.add_env_var(
1481            "PWD".into(),
1482            Value::test_string(path.as_ref().to_str().unwrap()),
1483        );
1484        engine_state
1485    }
1486
1487    /// Create a stack initialized with the given PWD.
1488    fn stack_with_pwd(path: impl AsRef<Path>) -> Stack {
1489        let mut stack = Stack::new();
1490        stack.add_env_var(
1491            "PWD".into(),
1492            Value::test_string(path.as_ref().to_str().unwrap()),
1493        );
1494        stack
1495    }
1496
1497    #[test]
1498    fn pwd_not_set() {
1499        let engine_state = EngineState::new();
1500        engine_state.cwd(None).unwrap_err();
1501    }
1502
1503    #[test]
1504    fn pwd_is_empty_string() {
1505        let engine_state = engine_state_with_pwd("");
1506        engine_state.cwd(None).unwrap_err();
1507    }
1508
1509    #[test]
1510    fn pwd_is_non_string_value() {
1511        let mut engine_state = EngineState::new();
1512        engine_state.add_env_var("PWD".into(), Value::test_glob("*"));
1513        engine_state.cwd(None).unwrap_err();
1514    }
1515
1516    #[test]
1517    fn pwd_is_relative_path() {
1518        let engine_state = engine_state_with_pwd("./foo");
1519
1520        engine_state.cwd(None).unwrap_err();
1521    }
1522
1523    #[test]
1524    fn pwd_has_trailing_slash() {
1525        let dir = TempDir::new().unwrap();
1526        let engine_state = engine_state_with_pwd(dir.path().join(""));
1527
1528        engine_state.cwd(None).unwrap_err();
1529    }
1530
1531    #[test]
1532    fn pwd_points_to_root() {
1533        #[cfg(windows)]
1534        let root = Path::new(r"C:\");
1535        #[cfg(not(windows))]
1536        let root = Path::new("/");
1537
1538        let engine_state = engine_state_with_pwd(root);
1539        let cwd = engine_state.cwd(None).unwrap();
1540        assert_path_eq!(cwd, root);
1541    }
1542
1543    #[test]
1544    fn pwd_points_to_normal_file() {
1545        let file = NamedTempFile::new().unwrap();
1546        let engine_state = engine_state_with_pwd(file.path());
1547
1548        engine_state.cwd(None).unwrap_err();
1549    }
1550
1551    #[test]
1552    fn pwd_points_to_normal_directory() {
1553        let dir = TempDir::new().unwrap();
1554        let engine_state = engine_state_with_pwd(dir.path());
1555
1556        let cwd = engine_state.cwd(None).unwrap();
1557        assert_path_eq!(cwd, dir.path());
1558    }
1559
1560    #[test]
1561    fn pwd_points_to_symlink_to_file() {
1562        let file = NamedTempFile::new().unwrap();
1563        let temp_file = AbsolutePath::try_new(file.path()).unwrap();
1564        let dir = TempDir::new().unwrap();
1565        let temp = AbsolutePath::try_new(dir.path()).unwrap();
1566
1567        let link = temp.join("link");
1568        symlink(temp_file, &link).unwrap();
1569        let engine_state = engine_state_with_pwd(&link);
1570
1571        engine_state.cwd(None).unwrap_err();
1572    }
1573
1574    #[test]
1575    fn pwd_points_to_symlink_to_directory() {
1576        let dir = TempDir::new().unwrap();
1577        let temp = AbsolutePath::try_new(dir.path()).unwrap();
1578
1579        let link = temp.join("link");
1580        symlink(temp, &link).unwrap();
1581        let engine_state = engine_state_with_pwd(&link);
1582
1583        let cwd = engine_state.cwd(None).unwrap();
1584        assert_path_eq!(cwd, link);
1585    }
1586
1587    #[test]
1588    fn pwd_points_to_broken_symlink() {
1589        let dir = TempDir::new().unwrap();
1590        let temp = AbsolutePath::try_new(dir.path()).unwrap();
1591        let other_dir = TempDir::new().unwrap();
1592        let other_temp = AbsolutePath::try_new(other_dir.path()).unwrap();
1593
1594        let link = temp.join("link");
1595        symlink(other_temp, &link).unwrap();
1596        let engine_state = engine_state_with_pwd(&link);
1597
1598        drop(other_dir);
1599        engine_state.cwd(None).unwrap_err();
1600    }
1601
1602    #[test]
1603    fn pwd_points_to_nonexistent_entity() {
1604        let engine_state = engine_state_with_pwd(TempDir::new().unwrap().path());
1605
1606        engine_state.cwd(None).unwrap_err();
1607    }
1608
1609    #[test]
1610    fn stack_pwd_not_set() {
1611        let dir = TempDir::new().unwrap();
1612        let engine_state = engine_state_with_pwd(dir.path());
1613        let stack = Stack::new();
1614
1615        let cwd = engine_state.cwd(Some(&stack)).unwrap();
1616        assert_eq!(cwd, dir.path());
1617    }
1618
1619    #[test]
1620    fn stack_pwd_is_empty_string() {
1621        let dir = TempDir::new().unwrap();
1622        let engine_state = engine_state_with_pwd(dir.path());
1623        let stack = stack_with_pwd("");
1624
1625        engine_state.cwd(Some(&stack)).unwrap_err();
1626    }
1627
1628    #[test]
1629    fn stack_pwd_points_to_normal_directory() {
1630        let dir1 = TempDir::new().unwrap();
1631        let dir2 = TempDir::new().unwrap();
1632        let engine_state = engine_state_with_pwd(dir1.path());
1633        let stack = stack_with_pwd(dir2.path());
1634
1635        let cwd = engine_state.cwd(Some(&stack)).unwrap();
1636        assert_path_eq!(cwd, dir2.path());
1637    }
1638
1639    #[test]
1640    fn stack_pwd_points_to_normal_directory_with_symlink_components() {
1641        let dir = TempDir::new().unwrap();
1642        let temp = AbsolutePath::try_new(dir.path()).unwrap();
1643
1644        // `/tmp/dir/link` points to `/tmp/dir`, then we set PWD to `/tmp/dir/link/foo`
1645        let link = temp.join("link");
1646        symlink(temp, &link).unwrap();
1647        let foo = link.join("foo");
1648        std::fs::create_dir(temp.join("foo")).unwrap();
1649        let engine_state = EngineState::new();
1650        let stack = stack_with_pwd(&foo);
1651
1652        let cwd = engine_state.cwd(Some(&stack)).unwrap();
1653        assert_path_eq!(cwd, foo);
1654    }
1655}