nu_protocol/engine/
engine_state.rs

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