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 nu_utils::time::Instant;
21use std::{
22 collections::{HashMap, HashSet},
23 num::NonZeroUsize,
24 path::PathBuf,
25 sync::{
26 Arc, Mutex, MutexGuard, PoisonError,
27 atomic::{AtomicBool, AtomicU32, Ordering},
28 mpsc::Sender,
29 mpsc::channel,
30 },
31 time::Duration,
32};
33
34type PoisonDebuggerError<'a> = PoisonError<MutexGuard<'a, Box<dyn Debugger>>>;
35
36#[cfg(feature = "plugin")]
37use crate::{PluginRegistryFile, PluginRegistryItem, RegisteredPlugin};
38
39use super::{CurrentJob, Jobs, Mail, Mailbox, ThreadJob};
40
41pub static UPDATE_CWD: AtomicBool = AtomicBool::new(true);
46
47#[derive(Clone, Debug)]
48pub enum VirtualPath {
49 File(FileId),
50 Dir(Vec<VirtualPathId>),
51}
52
53#[derive(Debug, Default)]
54pub struct ReplState {
55 pub buffer: String,
56 pub cursor_pos: usize,
58 pub accept: bool,
60}
61
62#[derive(Debug)]
63pub struct IsDebugging(AtomicBool);
64
65impl IsDebugging {
66 pub fn new(val: bool) -> Self {
67 IsDebugging(AtomicBool::new(val))
68 }
69}
70
71impl Clone for IsDebugging {
72 fn clone(&self) -> Self {
73 IsDebugging(AtomicBool::new(self.0.load(Ordering::Relaxed)))
74 }
75}
76
77#[derive(Clone, derive_more::Debug)]
95pub struct EngineState {
96 files: Vec<CachedFile>,
97 pub(super) virtual_paths: Vec<(String, VirtualPath)>,
98 vars: Vec<Variable>,
99 #[debug("{:?}", decls.iter().map(|c| c.name()).collect::<Vec<_>>())]
100 decls: Arc<Vec<Box<dyn Command + 'static>>>,
101 #[debug("{:?}", blocks.iter().map(|b| &b.signature.name))]
105 pub(super) blocks: Arc<Vec<Arc<Block>>>,
106 #[debug("{:?}", modules.iter().map(|m| String::from_utf8_lossy(&m.name)))]
107 pub(super) modules: Arc<Vec<Arc<Module>>>,
108 pub spans: Vec<Span>,
109 doccomments: Doccomments,
110 pub scope: ScopeFrame,
111 signals: Signals,
112 pub signal_handlers: Option<Handlers>,
113 pub env_vars: Arc<EnvVars>,
114 pub previous_env_vars: Arc<HashMap<EnvName, Value>>,
115 pub config: Arc<Config>,
117 config_epoch: u64,
119 pub pipeline_externals_state: Arc<(AtomicU32, AtomicU32)>,
120 pub repl_state: Arc<Mutex<ReplState>>,
121 pub prompt_state: Arc<PromptState>,
127 pub table_decl_id: Option<DeclId>,
128 #[cfg(feature = "plugin")]
129 pub plugin_path: Option<PathBuf>,
130 #[cfg(feature = "plugin")]
131 #[debug("{:?}", plugins.iter().map(|rp| rp.identity().name()).collect::<Vec<_>>())]
132 plugins: Vec<Arc<dyn RegisteredPlugin>>,
133 pub config_dirs: NushellConfigDirs,
139
140 pub history_enabled: bool,
141 pub history_session_id: i64,
142 pub history_locked_after_startup: bool,
150 pub file: Option<PathBuf>,
152 pub regex_cache: Arc<Mutex<LruCache<String, Regex>>>,
153 pub is_interactive: bool,
154 pub capture_repl_last_result: bool,
159 pub is_login: bool,
160 pub is_lsp: bool,
161 pub is_mcp: bool,
162 pub is_dap: bool,
166 startup_start: Instant,
169 startup_time: Option<Duration>,
171 is_debugging: IsDebugging,
172 pub debugger: Arc<Mutex<Box<dyn Debugger>>>,
173 pub report_log: Arc<Mutex<ReportLog>>,
174
175 pub jobs: Arc<Mutex<Jobs>>,
176
177 pub current_job: CurrentJob,
179
180 pub root_job_sender: Sender<Mail>,
181
182 pub exit_warning_given: Arc<AtomicBool>,
189}
190
191const REGEX_CACHE_SIZE: usize = 100; pub const NU_VARIABLE_ID: VarId = VarId::new(0);
195pub const IN_VARIABLE_ID: VarId = VarId::new(1);
196pub const ENV_VARIABLE_ID: VarId = VarId::new(2);
197pub const LAST_VARIABLE_ID: VarId = VarId::new(3);
202pub const LAST_RESULT_VAR_NAME: &str = "ans";
213pub const UNKNOWN_SPAN_ID: SpanId = SpanId::new(0);
218
219impl EngineState {
220 pub fn new() -> Self {
221 let (send, recv) = channel::<Mail>();
222
223 Self {
224 files: vec![],
225 virtual_paths: vec![],
226 vars: vec![
227 Variable::new(Span::new(0, 0), Type::Any, false),
228 Variable::new(Span::new(0, 0), Type::Any, false),
229 Variable::new(Span::new(0, 0), Type::Any, false),
230 Variable::new(Span::new(0, 0), Type::Any, false),
231 Variable::new(Span::new(0, 0), Type::Any, false),
232 ],
233 decls: Arc::new(vec![]),
234 blocks: Arc::new(vec![]),
235 modules: Arc::new(vec![Arc::new(Module::new(
236 DEFAULT_OVERLAY_NAME.as_bytes().to_vec(),
237 ))]),
238 spans: vec![Span::unknown()],
239 doccomments: Doccomments::new(),
240 scope: ScopeFrame::with_empty_overlay(
242 DEFAULT_OVERLAY_NAME.as_bytes().to_vec(),
243 ModuleId::new(0),
244 false,
245 ),
246 signal_handlers: None,
247 signals: Signals::empty(),
248 env_vars: Arc::new(
249 [(DEFAULT_OVERLAY_NAME.to_string(), HashMap::new())]
250 .into_iter()
251 .collect(),
252 ),
253 previous_env_vars: Arc::new(HashMap::new()),
254 config: Arc::new(Config::default()),
255 config_epoch: 0,
256 pipeline_externals_state: Arc::new((AtomicU32::new(0), AtomicU32::new(0))),
257 repl_state: Arc::new(Mutex::new(ReplState {
258 buffer: "".to_string(),
259 cursor_pos: 0,
260 accept: false,
261 })),
262 prompt_state: Arc::new(PromptState::new()),
263 table_decl_id: None,
264 #[cfg(feature = "plugin")]
265 plugin_path: None,
266 #[cfg(feature = "plugin")]
267 plugins: vec![],
268 config_dirs: NushellConfigDirs::empty(),
269 history_enabled: true,
270 history_session_id: 0,
271 history_locked_after_startup: false,
272 file: None,
273 regex_cache: Arc::new(Mutex::new(LruCache::new(
274 NonZeroUsize::new(REGEX_CACHE_SIZE).expect("tried to create cache of size zero"),
275 ))),
276 is_interactive: false,
277 capture_repl_last_result: false,
278 is_login: false,
279 is_lsp: false,
280 is_mcp: false,
281 is_dap: false,
282 startup_start: Instant::now(),
283 startup_time: None,
284 is_debugging: IsDebugging::new(false),
285 debugger: Arc::new(Mutex::new(Box::new(NoopDebugger))),
286 report_log: Arc::default(),
287 jobs: Arc::new(Mutex::new(Jobs::default())),
288 current_job: CurrentJob {
289 id: JobId::new(0),
290 background_thread_job: None,
291 mailbox: Arc::new(Mutex::new(Mailbox::new(recv))),
292 },
293 root_job_sender: send,
294 exit_warning_given: Arc::new(AtomicBool::new(false)),
295 }
296 }
297
298 pub fn signals(&self) -> &Signals {
299 &self.signals
300 }
301
302 pub fn get_cached_regex(&self, pattern: &str) -> Result<Regex, fancy_regex::Error> {
306 match self.regex_cache.try_lock() {
307 Ok(mut cache) => cache
308 .try_get_or_insert_ref(pattern, || Regex::new(pattern))
309 .cloned(),
310 Err(_) => Regex::new(pattern),
311 }
312 }
313
314 pub fn compile_regex(&self, pattern: &str, span: Span) -> Result<Regex, ShellError> {
317 self.get_cached_regex(pattern)
318 .map_err(|err| invalid_regex_value(pattern, err, span))
319 }
320
321 pub fn reset_signals(&mut self) {
322 self.signals.reset();
323 if let Some(ref handlers) = self.signal_handlers {
324 handlers.run(SignalAction::Reset);
325 }
326 }
327
328 pub fn set_signals(&mut self, signals: Signals) {
329 self.signals = signals;
330 }
331
332 pub fn merge_delta(&mut self, mut delta: StateDelta) -> Result<(), ShellError> {
340 self.files.extend(delta.files);
342 self.virtual_paths.extend(delta.virtual_paths);
343 self.vars.extend(delta.vars);
344 self.spans.extend(delta.spans);
345 self.doccomments.merge_with(delta.doccomments);
346
347 if !delta.decls.is_empty() {
349 Arc::make_mut(&mut self.decls).extend(delta.decls);
350 }
351 if !delta.blocks.is_empty() {
352 Arc::make_mut(&mut self.blocks).extend(delta.blocks);
353 }
354 if !delta.modules.is_empty() {
355 Arc::make_mut(&mut self.modules).extend(delta.modules);
356 }
357
358 let first = delta.scope.remove(0);
359
360 for (delta_name, delta_overlay) in first.clone().overlays {
361 if let Some((_, existing_overlay)) = self
362 .scope
363 .overlays
364 .iter_mut()
365 .find(|(name, _)| name == &delta_name)
366 {
367 for item in delta_overlay.decls.into_iter() {
369 existing_overlay.decls.insert(item.0, item.1);
370 }
371 for item in delta_overlay.vars.into_iter() {
372 existing_overlay.insert_variable(item.0, item.1);
373 }
374 for item in delta_overlay.modules.into_iter() {
375 existing_overlay.modules.insert(item.0, item.1);
376 }
377
378 existing_overlay
379 .visibility
380 .merge_with(delta_overlay.visibility);
381 } else {
382 self.scope.overlays.push((delta_name, delta_overlay));
384 }
385 }
386
387 let mut activated_ids = self.translate_overlay_ids(&first);
388
389 let mut removed_ids = vec![];
390
391 for name in &first.removed_overlays {
392 if let Some(overlay_id) = self.find_overlay(name) {
393 removed_ids.push(overlay_id);
394 }
395 }
396
397 self.scope
399 .active_overlays
400 .retain(|id| !removed_ids.contains(id));
401
402 self.scope
404 .active_overlays
405 .retain(|id| !activated_ids.contains(id));
406 self.scope.active_overlays.append(&mut activated_ids);
407
408 #[cfg(feature = "plugin")]
409 if !delta.plugins.is_empty() {
410 for plugin in std::mem::take(&mut delta.plugins) {
411 if let Some(handlers) = &self.signal_handlers {
413 plugin.clone().configure_signal_handler(handlers)?;
414 }
415
416 if let Some(existing) = self
418 .plugins
419 .iter_mut()
420 .find(|p| p.identity().name() == plugin.identity().name())
421 {
422 existing.stop()?;
424 *existing = plugin;
425 } else {
426 self.plugins.push(plugin);
427 }
428 }
429 }
430
431 #[cfg(feature = "plugin")]
432 if !delta.plugin_registry_items.is_empty() {
433 if self.plugin_path.is_some() {
435 self.update_plugin_file(std::mem::take(&mut delta.plugin_registry_items))?;
436 }
437 }
438
439 Ok(())
440 }
441
442 pub fn merge_env(&mut self, stack: &mut Stack) -> Result<(), ShellError> {
444 for mut scope in stack.env_vars.drain(..) {
445 for (overlay_name, mut env) in Arc::make_mut(&mut scope).drain() {
446 if let Some(env_vars) = Arc::make_mut(&mut self.env_vars).get_mut(&overlay_name) {
447 env_vars.extend(env.drain());
449 } else {
450 Arc::make_mut(&mut self.env_vars).insert(overlay_name, env);
452 }
453 }
454 }
455
456 if UPDATE_CWD.load(Ordering::Relaxed) {
457 let cwd = self.cwd(Some(stack))?;
458 std::env::set_current_dir(cwd)
459 .map_err(|err| IoError::new_internal(err, "Could not set current dir"))?;
460 }
461
462 if let Some(config) = stack.config.take() {
463 self.replace_config(config);
465
466 #[cfg(feature = "plugin")]
468 self.update_plugin_gc_configs(&self.config.plugin_gc);
469 }
470
471 Ok(())
472 }
473
474 pub fn cleanup_stack_variables(&mut self, stack: &mut Stack) {
477 let mut shadowed_vars = HashSet::new();
478 for (_, frame) in self.scope.overlays.iter_mut() {
479 shadowed_vars.extend(frame.shadowed_vars.drain(..));
480 }
481
482 if shadowed_vars.is_empty() {
483 return;
484 }
485
486 let mut alias_var_ids = HashSet::new();
490 for decl in self.decls.iter() {
491 if let Some(alias) = decl.as_alias() {
492 collect_alias_var_ids(&alias.wrapped_call, &mut alias_var_ids);
493 }
494 }
495
496 stack.vars.retain(|(var_id, _)| {
498 !shadowed_vars.contains(var_id) || alias_var_ids.contains(var_id)
499 });
500 }
501
502 pub fn active_overlay_ids<'a, 'b>(
503 &'b self,
504 removed_overlays: &'a [Vec<u8>],
505 ) -> impl DoubleEndedIterator<Item = &'b OverlayId> + 'a
506 where
507 'b: 'a,
508 {
509 self.scope.active_overlays.iter().filter(|id| {
510 !removed_overlays
511 .iter()
512 .any(|name| name == self.get_overlay_name(**id))
513 })
514 }
515
516 pub fn active_overlays<'a, 'b>(
517 &'b self,
518 removed_overlays: &'a [Vec<u8>],
519 ) -> impl DoubleEndedIterator<Item = &'b OverlayFrame> + 'a
520 where
521 'b: 'a,
522 {
523 self.active_overlay_ids(removed_overlays)
524 .map(|id| self.get_overlay(*id))
525 }
526
527 pub fn active_overlay_names<'a, 'b>(
528 &'b self,
529 removed_overlays: &'a [Vec<u8>],
530 ) -> impl DoubleEndedIterator<Item = &'b [u8]> + 'a
531 where
532 'b: 'a,
533 {
534 self.active_overlay_ids(removed_overlays)
535 .map(|id| self.get_overlay_name(*id))
536 }
537
538 fn translate_overlay_ids(&self, other: &ScopeFrame) -> Vec<OverlayId> {
540 let other_names = other.active_overlays.iter().map(|other_id| {
541 &other
542 .overlays
543 .get(other_id.get())
544 .expect("internal error: missing overlay")
545 .0
546 });
547
548 other_names
549 .map(|other_name| {
550 self.find_overlay(other_name)
551 .expect("internal error: missing overlay")
552 })
553 .collect()
554 }
555
556 pub fn last_overlay_name(&self, removed_overlays: &[Vec<u8>]) -> &[u8] {
557 self.active_overlay_names(removed_overlays)
558 .last()
559 .expect("internal error: no active overlays")
560 }
561
562 pub fn last_overlay(&self, removed_overlays: &[Vec<u8>]) -> &OverlayFrame {
563 self.active_overlay_ids(removed_overlays)
564 .last()
565 .map(|id| self.get_overlay(*id))
566 .expect("internal error: no active overlays")
567 }
568
569 pub fn get_overlay_name(&self, overlay_id: OverlayId) -> &[u8] {
570 &self
571 .scope
572 .overlays
573 .get(overlay_id.get())
574 .expect("internal error: missing overlay")
575 .0
576 }
577
578 pub fn get_overlay(&self, overlay_id: OverlayId) -> &OverlayFrame {
579 &self
580 .scope
581 .overlays
582 .get(overlay_id.get())
583 .expect("internal error: missing overlay")
584 .1
585 }
586
587 pub fn render_env_vars(&self) -> HashMap<&str, &Value> {
588 let mut result: HashMap<&str, &Value> = HashMap::new();
589
590 for overlay_name in self.active_overlay_names(&[]) {
591 let name = String::from_utf8_lossy(overlay_name);
592 if let Some(env_vars) = self.env_vars.get(name.as_ref()) {
593 result.extend(env_vars.iter().map(|(k, v)| (k.as_str(), v)));
594 }
595 }
596
597 result
598 }
599
600 pub fn add_env_var(&mut self, name: String, val: Value) {
601 let overlay_name = String::from_utf8_lossy(self.last_overlay_name(&[])).to_string();
602
603 if let Some(env_vars) = Arc::make_mut(&mut self.env_vars).get_mut(&overlay_name) {
604 env_vars.insert(EnvName::from(name), val);
605 } else {
606 Arc::make_mut(&mut self.env_vars).insert(
607 overlay_name,
608 [(EnvName::from(name), val)].into_iter().collect(),
609 );
610 }
611 }
612
613 pub fn get_env_var(&self, name: &str) -> Option<&Value> {
614 for overlay_id in self.scope.active_overlays.iter().rev() {
615 let overlay_name = String::from_utf8_lossy(self.get_overlay_name(*overlay_id));
616 if let Some(env_vars) = self.env_vars.get(overlay_name.as_ref())
617 && let Some(val) = env_vars.get(&EnvName::from(name))
618 {
619 return Some(val);
620 }
621 }
622
623 None
624 }
625
626 #[cfg(feature = "plugin")]
627 pub fn plugins(&self) -> &[Arc<dyn RegisteredPlugin>] {
628 &self.plugins
629 }
630
631 #[cfg(feature = "plugin")]
632 fn update_plugin_file(&self, updated_items: Vec<PluginRegistryItem>) -> Result<(), ShellError> {
633 use std::fs::File;
635
636 let plugin_path = self.plugin_path.as_ref().ok_or_else(|| {
637 ShellError::Generic(
638 GenericError::new_internal("Plugin file path not set", "")
639 .with_help("you may be running nu with --no-config-file"),
640 )
641 })?;
642
643 let mut contents = match File::open(plugin_path.as_path()) {
645 Ok(mut plugin_file) => PluginRegistryFile::read_from(&mut plugin_file, None),
646 Err(err) => {
647 if err.kind() == std::io::ErrorKind::NotFound {
648 Ok(PluginRegistryFile::default())
649 } else {
650 Err(ShellError::Io(IoError::new_internal_with_path(
651 err,
652 "Failed to open plugin file",
653 PathBuf::from(plugin_path),
654 )))
655 }
656 }
657 }?;
658
659 for item in updated_items {
661 contents.upsert_plugin(item);
662 }
663
664 let plugin_file = File::create(plugin_path.as_path()).map_err(|err| {
666 IoError::new_internal_with_path(
667 err,
668 "Failed to write plugin file",
669 PathBuf::from(plugin_path),
670 )
671 })?;
672
673 contents.write_to(plugin_file, None)
674 }
675
676 #[cfg(feature = "plugin")]
678 fn update_plugin_gc_configs(&self, plugin_gc: &crate::PluginGcConfigs) {
679 for plugin in &self.plugins {
680 plugin.set_gc_config(plugin_gc.get(plugin.identity().name()));
681 }
682 }
683
684 pub fn num_files(&self) -> usize {
685 self.files.len()
686 }
687
688 pub fn num_virtual_paths(&self) -> usize {
689 self.virtual_paths.len()
690 }
691
692 pub fn num_vars(&self) -> usize {
693 self.vars.len()
694 }
695
696 pub fn num_decls(&self) -> usize {
697 self.decls.len()
698 }
699
700 pub fn num_blocks(&self) -> usize {
701 self.blocks.len()
702 }
703
704 pub fn num_modules(&self) -> usize {
705 self.modules.len()
706 }
707
708 pub fn num_spans(&self) -> usize {
709 self.spans.len()
710 }
711 pub fn print_vars(&self) {
712 for var in self.vars.iter().enumerate() {
713 println!("var{}: {:?}", var.0, var.1);
714 }
715 }
716
717 pub fn print_decls(&self) {
718 for decl in self.decls.iter().enumerate() {
719 println!("decl{}: {:?}", decl.0, decl.1.signature());
720 }
721 }
722
723 pub fn print_blocks(&self) {
724 for block in self.blocks.iter().enumerate() {
725 println!("block{}: {:?}", block.0, block.1);
726 }
727 }
728
729 pub fn print_contents(&self) {
730 for cached_file in self.files.iter() {
731 let string = String::from_utf8_lossy(&cached_file.content);
732 println!("{string}");
733 }
734 }
735
736 pub fn find_decl(&self, name: &[u8], removed_overlays: &[Vec<u8>]) -> Option<DeclId> {
740 let mut visibility = VisibilityStack::default();
741
742 for overlay_frame in self.active_overlays(removed_overlays).rev() {
743 visibility.push(&overlay_frame.visibility);
744
745 if let Some(decl_id) = overlay_frame.get_decl(name)
746 && visibility.is_decl_id_visible(&decl_id)
747 {
748 return Some(decl_id);
749 }
750 }
751
752 None
753 }
754
755 pub fn find_decl_name(&self, decl_id: DeclId, removed_overlays: &[Vec<u8>]) -> Option<&[u8]> {
759 let mut visibility = VisibilityStack::default();
760
761 for overlay_frame in self.active_overlays(removed_overlays).rev() {
762 visibility.push(&overlay_frame.visibility);
763
764 if visibility.is_decl_id_visible(&decl_id) {
765 for (name, id) in overlay_frame.decls.iter() {
766 if id == &decl_id {
767 return Some(name);
768 }
769 }
770 }
771 }
772
773 None
774 }
775
776 pub fn find_overlay(&self, name: &[u8]) -> Option<OverlayId> {
780 self.scope.find_overlay(name)
781 }
782
783 pub fn find_active_overlay(&self, name: &[u8]) -> Option<OverlayId> {
787 self.scope.find_active_overlay(name)
788 }
789
790 pub fn find_module(&self, name: &[u8], removed_overlays: &[Vec<u8>]) -> Option<ModuleId> {
794 for overlay_frame in self.active_overlays(removed_overlays).rev() {
795 if let Some(module_id) = overlay_frame.modules.get(name) {
796 return Some(*module_id);
797 }
798 }
799
800 None
801 }
802
803 pub fn get_module_comments(&self, module_id: ModuleId) -> Option<&[Span]> {
804 self.doccomments.get_module_comments(module_id)
805 }
806
807 #[cfg(feature = "plugin")]
808 pub fn plugin_decls(&self) -> impl Iterator<Item = &Box<dyn Command + 'static>> {
809 let mut unique_plugin_decls = HashMap::new();
810
811 for decl in self.decls.iter().filter(|d| d.is_plugin()) {
813 unique_plugin_decls.insert(decl.name(), decl);
814 }
815
816 let mut plugin_decls: Vec<(&str, &Box<dyn Command>)> =
817 unique_plugin_decls.into_iter().collect();
818
819 plugin_decls.sort_by(|a, b| a.0.cmp(b.0));
821 plugin_decls.into_iter().map(|(_, decl)| decl)
822 }
823
824 pub fn which_module_has_decl(
825 &self,
826 decl_name: &[u8],
827 removed_overlays: &[Vec<u8>],
828 ) -> Option<&[u8]> {
829 for overlay_frame in self.active_overlays(removed_overlays).rev() {
830 for (module_name, module_id) in overlay_frame.modules.iter() {
831 let module = self.get_module(*module_id);
832 if module.has_decl(decl_name) {
833 return Some(module_name);
834 }
835 }
836 }
837
838 None
839 }
840
841 pub fn traverse_commands(&self, mut f: impl FnMut(&[u8], DeclId)) {
843 for overlay_frame in self.active_overlays(&[]).rev() {
844 for (name, decl_id) in &overlay_frame.decls {
845 if overlay_frame.visibility.is_decl_id_visible(decl_id) {
846 f(name, *decl_id);
847 }
848 }
849 }
850 }
851
852 pub fn get_span_contents(&self, span: Span) -> &[u8] {
853 self.try_get_file_contents(span).unwrap_or(&[0u8; 0])
854 }
855
856 pub fn try_get_file_contents(&self, span: Span) -> Option<&[u8]> {
857 self.files.iter().find_map(|file| {
858 if file.covered_span.contains_span(span) {
859 let start = span.start - file.covered_span.start;
860 let end = span.end - file.covered_span.start;
861 Some(&file.content[start..end])
862 } else {
863 None
864 }
865 })
866 }
867
868 pub fn span_match_prefix(&self, span: Span, prefix: &[u8]) -> Option<(Span, Span)> {
871 let contents = self.get_span_contents(span);
872
873 if contents.starts_with(prefix) {
874 span.split_at(prefix.len())
875 } else {
876 None
877 }
878 }
879
880 pub fn span_match_postfix(&self, span: Span, prefix: &[u8]) -> Option<(Span, Span)> {
883 let contents = self.get_span_contents(span);
884
885 if contents.ends_with(prefix) {
886 span.split_at(span.len() - prefix.len())
887 } else {
888 None
889 }
890 }
891
892 pub fn get_config(&self) -> &Arc<Config> {
897 &self.config
898 }
899
900 pub fn config_epoch(&self) -> u64 {
902 self.config_epoch
903 }
904
905 fn replace_config(&mut self, conf: Arc<Config>) {
906 self.config_epoch = self.config_epoch.wrapping_add(1);
907 self.config = conf;
908 }
909
910 pub fn set_config(&mut self, conf: impl Into<Arc<Config>>) {
911 let conf = conf.into();
912
913 #[cfg(feature = "plugin")]
914 if conf.plugin_gc != self.config.plugin_gc {
915 self.update_plugin_gc_configs(&conf.plugin_gc);
917 }
918
919 self.replace_config(conf);
920 }
921
922 pub fn get_plugin_config(&self, plugin: &str) -> Option<&Value> {
927 self.config.plugins.get(plugin)
928 }
929
930 pub fn history_config(&self) -> Option<HistoryConfig> {
932 self.history_enabled.then(|| self.config.history.clone())
933 }
934
935 pub fn history_path(&self) -> Option<std::path::PathBuf> {
940 self.history_config()?
941 .file_path(&self.config_dirs.config_home)
942 }
943
944 pub fn get_var(&self, var_id: VarId) -> &Variable {
945 self.vars
946 .get(var_id.get())
947 .expect("internal error: missing variable")
948 }
949
950 pub fn get_constant(&self, var_id: VarId) -> Option<&Value> {
951 let var = self.get_var(var_id);
952 var.const_val.as_ref()
953 }
954
955 pub fn generate_nu_constant(&mut self) {
956 self.vars[NU_VARIABLE_ID.get()].const_val = Some(create_nu_constant(self, Span::unknown()));
957 }
958
959 pub fn get_decl(&self, decl_id: DeclId) -> &dyn Command {
960 self.decls
961 .get(decl_id.get())
962 .expect("internal error: missing declaration")
963 .as_ref()
964 }
965
966 pub fn get_decls_sorted(&self, include_hidden: bool) -> Vec<(Vec<u8>, DeclId)> {
968 let mut decls_map = HashMap::new();
969
970 for overlay_frame in self.active_overlays(&[]) {
971 let new_decls = if include_hidden {
972 overlay_frame.decls.clone()
973 } else {
974 overlay_frame
975 .decls
976 .clone()
977 .into_iter()
978 .filter(|(_, id)| overlay_frame.visibility.is_decl_id_visible(id))
979 .collect()
980 };
981
982 decls_map.extend(new_decls);
983 }
984
985 let mut decls: Vec<(Vec<u8>, DeclId)> = decls_map.into_iter().collect();
986
987 decls.sort_by(|a, b| a.0.cmp(&b.0));
988 decls
989 }
990
991 pub fn get_signature(&self, decl: &dyn Command) -> Signature {
992 if let Some(block_id) = decl.block_id() {
993 *self.blocks[block_id.get()].signature.clone()
994 } else {
995 decl.signature()
996 }
997 }
998
999 pub fn get_signatures_and_declids(&self, include_hidden: bool) -> Vec<(Signature, DeclId)> {
1001 self.get_decls_sorted(include_hidden)
1002 .into_iter()
1003 .map(|(_, id)| {
1004 let decl = self.get_decl(id);
1005
1006 (self.get_signature(decl).update_from_command(decl), id)
1007 })
1008 .collect()
1009 }
1010
1011 pub fn get_block(&self, block_id: BlockId) -> &Arc<Block> {
1012 self.blocks
1013 .get(block_id.get())
1014 .expect("internal error: missing block")
1015 }
1016
1017 pub fn try_get_block(&self, block_id: BlockId) -> Option<&Arc<Block>> {
1023 self.blocks.get(block_id.get())
1024 }
1025
1026 pub fn get_module(&self, module_id: ModuleId) -> &Module {
1027 self.modules
1028 .get(module_id.get())
1029 .expect("internal error: missing module")
1030 }
1031
1032 pub fn get_virtual_path(&self, virtual_path_id: VirtualPathId) -> &(String, VirtualPath) {
1033 self.virtual_paths
1034 .get(virtual_path_id.get())
1035 .expect("internal error: missing virtual path")
1036 }
1037
1038 pub fn next_span_start(&self) -> usize {
1039 if let Some(cached_file) = self.files.last() {
1040 cached_file.covered_span.end
1041 } else {
1042 0
1043 }
1044 }
1045
1046 pub fn files(
1047 &self,
1048 ) -> impl DoubleEndedIterator<Item = &CachedFile> + ExactSizeIterator<Item = &CachedFile> {
1049 self.files.iter()
1050 }
1051
1052 pub fn add_file(&mut self, filename: Arc<str>, content: Arc<[u8]>) -> FileId {
1053 let next_span_start = self.next_span_start();
1054 let next_span_end = next_span_start + content.len();
1055
1056 let covered_span = Span::new(next_span_start, next_span_end);
1057
1058 self.files.push(CachedFile {
1059 name: filename,
1060 content,
1061 covered_span,
1062 });
1063
1064 FileId::new(self.num_files() - 1)
1065 }
1066
1067 pub fn build_desc(&self, spans: &[Span]) -> (String, String) {
1068 let comment_lines: Vec<&[u8]> = spans
1069 .iter()
1070 .map(|span| self.get_span_contents(*span))
1071 .collect();
1072 build_desc(&comment_lines)
1073 }
1074
1075 pub fn build_module_desc(&self, module_id: ModuleId) -> Option<(String, String)> {
1076 self.get_module_comments(module_id)
1077 .map(|comment_spans| self.build_desc(comment_spans))
1078 }
1079
1080 #[deprecated(since = "0.92.3", note = "please use `EngineState::cwd()` instead")]
1084 pub fn current_work_dir(&self) -> String {
1085 self.cwd(None)
1086 .map(|path| path.to_string_lossy().to_string())
1087 .unwrap_or_default()
1088 }
1089
1090 pub fn cwd(&self, stack: Option<&Stack>) -> Result<AbsolutePathBuf, ShellError> {
1097 fn error(msg: &str, cwd: impl AsRef<nu_path::Path>) -> ShellError {
1099 ShellError::Generic(
1100 GenericError::new_internal(
1101 msg.to_string(),
1102 format!("$env.PWD = {}", cwd.as_ref().display()),
1103 )
1104 .with_help("Use `cd` to reset $env.PWD into a good state"),
1105 )
1106 }
1107
1108 let pwd = if let Some(stack) = stack {
1110 stack.get_env_var(self, "PWD")
1111 } else {
1112 self.get_env_var("PWD")
1113 };
1114
1115 let pwd = pwd.ok_or_else(|| error("$env.PWD not found", ""))?;
1116
1117 if let Ok(pwd) = pwd.as_str() {
1118 let path = AbsolutePathBuf::try_from(pwd)
1119 .map_err(|_| error("$env.PWD is not an absolute path", pwd))?;
1120
1121 if path.parent().is_some() && nu_path::has_trailing_slash(path.as_ref()) {
1124 Err(error("$env.PWD contains trailing slashes", &path))
1125 } else if !path.exists() {
1126 Err(error("$env.PWD points to a non-existent directory", &path))
1127 } else if !path.is_dir() {
1128 Err(error("$env.PWD points to a non-directory", &path))
1129 } else {
1130 Ok(path)
1131 }
1132 } else {
1133 Err(error("$env.PWD is not a string", format!("{pwd:?}")))
1134 }
1135 }
1136
1137 pub fn cwd_as_string(&self, stack: Option<&Stack>) -> Result<String, ShellError> {
1139 let cwd = self.cwd(stack)?;
1140 cwd.into_os_string()
1141 .into_string()
1142 .map_err(|err| ShellError::NonUtf8Custom {
1143 msg: format!("The current working directory is not a valid utf-8 string: {err:?}"),
1144 span: Span::unknown(),
1145 })
1146 }
1147
1148 pub fn get_file_contents(&self) -> &[CachedFile] {
1150 &self.files
1151 }
1152
1153 pub fn startup_time(&self) -> Duration {
1160 self.startup_time
1161 .unwrap_or_else(|| self.startup_start.elapsed())
1162 }
1163
1164 pub fn set_startup_start(&mut self, startup_start: Instant) {
1166 self.startup_start = startup_start;
1167 }
1168
1169 pub fn finish_startup(&mut self) -> Duration {
1173 let startup_time = *self
1174 .startup_time
1175 .get_or_insert_with(|| self.startup_start.elapsed());
1176 self.generate_nu_constant();
1177 startup_time
1178 }
1179
1180 pub fn activate_debugger(
1181 &self,
1182 debugger: Box<dyn Debugger>,
1183 ) -> Result<(), PoisonDebuggerError<'_>> {
1184 let mut locked_debugger = self.debugger.lock()?;
1185 *locked_debugger = debugger;
1186 locked_debugger.activate();
1187 self.is_debugging.0.store(true, Ordering::Relaxed);
1188 Ok(())
1189 }
1190
1191 pub fn deactivate_debugger(&self) -> Result<Box<dyn Debugger>, PoisonDebuggerError<'_>> {
1192 let mut locked_debugger = self.debugger.lock()?;
1193 locked_debugger.deactivate();
1194 let ret = std::mem::replace(&mut *locked_debugger, Box::new(NoopDebugger));
1195 self.is_debugging.0.store(false, Ordering::Relaxed);
1196 Ok(ret)
1197 }
1198
1199 pub fn is_debugging(&self) -> bool {
1200 self.is_debugging.0.load(Ordering::Relaxed)
1201 }
1202
1203 pub fn recover_from_panic(&mut self) {
1204 if Mutex::is_poisoned(&self.repl_state) {
1205 self.repl_state = Arc::new(Mutex::new(ReplState {
1206 buffer: "".to_string(),
1207 cursor_pos: 0,
1208 accept: false,
1209 }));
1210 }
1211 if Mutex::is_poisoned(&self.jobs) {
1212 self.jobs = Arc::new(Mutex::new(Jobs::default()));
1213 }
1214 if Mutex::is_poisoned(&self.regex_cache) {
1215 self.regex_cache = Arc::new(Mutex::new(LruCache::new(
1216 NonZeroUsize::new(REGEX_CACHE_SIZE).expect("tried to create cache of size zero"),
1217 )));
1218 }
1219 }
1220
1221 pub fn make_session_state_unique(&mut self) {
1223 let (send, recv) = channel();
1224
1225 self.pipeline_externals_state = Arc::new((AtomicU32::new(0), AtomicU32::new(0)));
1226 self.repl_state = Default::default();
1227 self.report_log = Default::default();
1228 self.prompt_state = Arc::new(PromptState::new());
1229 self.jobs = Default::default();
1230 self.current_job = CurrentJob {
1231 id: JobId::new(0),
1232 background_thread_job: None,
1233 mailbox: Arc::new(Mutex::new(Mailbox::new(recv))),
1234 };
1235 self.root_job_sender = send;
1236 self.exit_warning_given = Default::default();
1237 self.regex_cache = Arc::new(Mutex::new(LruCache::new(
1238 NonZeroUsize::new(REGEX_CACHE_SIZE).expect("tried to create cache of size zero"),
1239 )));
1240 self.is_debugging = IsDebugging::new(false);
1241 self.debugger = Arc::new(Mutex::new(Box::new(NoopDebugger)));
1242 }
1243
1244 pub fn add_span(&mut self, span: Span) -> SpanId {
1246 self.spans.push(span);
1247 SpanId::new(self.num_spans() - 1)
1248 }
1249
1250 pub fn find_span_id(&self, span: Span) -> Option<SpanId> {
1252 self.spans
1253 .iter()
1254 .position(|sp| sp == &span)
1255 .map(SpanId::new)
1256 }
1257
1258 pub fn is_background_job(&self) -> bool {
1260 self.current_job.background_thread_job.is_some()
1261 }
1262
1263 pub fn current_thread_job(&self) -> Option<&ThreadJob> {
1265 self.current_job.background_thread_job.as_ref()
1266 }
1267}
1268
1269fn collect_alias_var_ids(expr: &crate::ast::Expression, var_ids: &mut HashSet<VarId>) {
1273 let mut queue = vec![expr];
1274
1275 while let Some(e) = queue.pop() {
1276 match &e.expr {
1277 Expr::Var(id) => {
1278 var_ids.insert(*id);
1279 }
1280 Expr::Call(call) => {
1281 for arg in &call.arguments {
1282 if let Some(sub_expr) = arg.expr() {
1283 queue.push(sub_expr);
1284 }
1285 }
1286 }
1287 Expr::ExternalCall(head, args) => {
1288 queue.push(head);
1289 for arg in args.iter() {
1290 queue.push(arg.expr());
1291 }
1292 }
1293 Expr::FullCellPath(fcp) => queue.push(&fcp.head),
1294 _ => {}
1295 }
1296 }
1297}
1298
1299impl GetSpan for &EngineState {
1300 fn get_span(&self, span_id: SpanId) -> Span {
1302 *self
1303 .spans
1304 .get(span_id.get())
1305 .expect("internal error: missing span")
1306 }
1307}
1308
1309impl Default for EngineState {
1310 fn default() -> Self {
1311 Self::new()
1312 }
1313}
1314
1315pub fn invalid_regex_value(pattern: &str, err: fancy_regex::Error, span: Span) -> ShellError {
1320 ShellError::InvalidValue {
1321 valid: "a valid regular expression".into(),
1322 actual: format!("'{pattern}' ({err})"),
1323 span,
1324 }
1325}
1326
1327#[cfg(test)]
1328mod engine_state_tests {
1329 use crate::engine::StateWorkingSet;
1330 use std::str::{Utf8Error, from_utf8};
1331
1332 use super::*;
1333
1334 #[test]
1335 fn add_file_gives_id() {
1336 let engine_state = EngineState::new();
1337 let mut engine_state = StateWorkingSet::new(&engine_state);
1338 let id = engine_state.add_file("test.nu", &[]);
1339
1340 assert_eq!(id, FileId::new(0));
1341 }
1342
1343 #[test]
1344 fn get_cached_regex_reuses_compiled_patterns() {
1345 let engine_state = EngineState::new();
1346 let pattern = r"[^\w]+";
1347
1348 let first = engine_state
1349 .get_cached_regex(pattern)
1350 .expect("pattern should compile");
1351 assert!(first.is_match("!!!").unwrap_or(false));
1352
1353 {
1354 let cache = engine_state.regex_cache.lock().expect("cache lock");
1355 assert_eq!(cache.len(), 1);
1356 assert!(cache.peek(pattern).is_some());
1357 }
1358
1359 let second = engine_state
1360 .get_cached_regex(pattern)
1361 .expect("pattern should compile from cache");
1362 assert!(second.is_match("!!!").unwrap_or(false));
1363
1364 {
1365 let cache = engine_state.regex_cache.lock().expect("cache lock");
1366 assert_eq!(cache.len(), 1, "second lookup should not grow the cache");
1367 }
1368 }
1369
1370 #[test]
1371 fn get_cached_regex_does_not_store_invalid_patterns() {
1372 let engine_state = EngineState::new();
1373
1374 assert!(engine_state.get_cached_regex("(").is_err());
1375
1376 let cache = engine_state.regex_cache.lock().expect("cache lock");
1377 assert_eq!(cache.len(), 0);
1378 }
1379
1380 #[test]
1381 fn add_file_gives_id_including_parent() {
1382 let mut engine_state = EngineState::new();
1383 let parent_id = engine_state.add_file("test.nu".into(), Arc::new([]));
1384
1385 let mut working_set = StateWorkingSet::new(&engine_state);
1386 let working_set_id = working_set.add_file("child.nu", &[]);
1387
1388 assert_eq!(parent_id, FileId::new(0));
1389 assert_eq!(working_set_id, FileId::new(1));
1390 }
1391
1392 #[test]
1393 fn merge_states() -> Result<(), ShellError> {
1394 let mut engine_state = EngineState::new();
1395 engine_state.add_file("test.nu".into(), Arc::new([]));
1396
1397 let delta = {
1398 let mut working_set = StateWorkingSet::new(&engine_state);
1399 let _ = working_set.add_file("child.nu", &[]);
1400 working_set.render()
1401 };
1402
1403 engine_state.merge_delta(delta)?;
1404
1405 assert_eq!(engine_state.num_files(), 2);
1406 assert_eq!(&*engine_state.files[0].name, "test.nu");
1407 assert_eq!(&*engine_state.files[1].name, "child.nu");
1408
1409 Ok(())
1410 }
1411
1412 #[test]
1413 fn list_variables() -> Result<(), Utf8Error> {
1414 let varname = "something";
1415 let varname_with_sigil = "$".to_owned() + varname;
1416 let engine_state = EngineState::new();
1417 let mut working_set = StateWorkingSet::new(&engine_state);
1418 working_set.add_variable(
1419 varname.as_bytes().into(),
1420 Span { start: 0, end: 1 },
1421 Type::Int,
1422 false,
1423 );
1424 let variables = working_set
1425 .list_variables()
1426 .into_iter()
1427 .map(from_utf8)
1428 .collect::<Result<Vec<&str>, Utf8Error>>()?;
1429 assert_eq!(variables, vec![varname_with_sigil]);
1430 Ok(())
1431 }
1432
1433 #[test]
1434 fn get_plugin_config() {
1435 let mut engine_state = EngineState::new();
1436
1437 assert!(
1438 engine_state.get_plugin_config("example").is_none(),
1439 "Unexpected plugin configuration"
1440 );
1441
1442 let mut plugins = HashMap::new();
1443 plugins.insert("example".into(), Value::string("value", Span::test_data()));
1444
1445 let mut config = Config::clone(engine_state.get_config());
1446 config.plugins = plugins;
1447
1448 engine_state.set_config(config);
1449
1450 assert!(
1451 engine_state.get_plugin_config("example").is_some(),
1452 "Plugin configuration not found"
1453 );
1454 }
1455}
1456
1457#[cfg(test)]
1458mod test_cwd {
1459 use crate::{
1474 Value,
1475 engine::{EngineState, Stack},
1476 };
1477 use nu_path::{AbsolutePath, Path, assert_path_eq};
1478 use tempfile::{NamedTempFile, TempDir};
1479
1480 #[cfg(any(unix, windows))]
1482 fn symlink(
1483 original: impl AsRef<AbsolutePath>,
1484 link: impl AsRef<AbsolutePath>,
1485 ) -> std::io::Result<()> {
1486 let original = original.as_ref();
1487 let link = link.as_ref();
1488
1489 #[cfg(unix)]
1490 {
1491 std::os::unix::fs::symlink(original, link)
1492 }
1493 #[cfg(windows)]
1494 {
1495 if original.is_dir() {
1496 std::os::windows::fs::symlink_dir(original, link)
1497 } else {
1498 std::os::windows::fs::symlink_file(original, link)
1499 }
1500 }
1501 }
1502
1503 fn engine_state_with_pwd(path: impl AsRef<Path>) -> EngineState {
1505 let mut engine_state = EngineState::new();
1506 engine_state.add_env_var(
1507 "PWD".into(),
1508 Value::test_string(path.as_ref().to_str().unwrap()),
1509 );
1510 engine_state
1511 }
1512
1513 fn stack_with_pwd(path: impl AsRef<Path>) -> Stack {
1515 let mut stack = Stack::new();
1516 stack.add_env_var(
1517 "PWD".into(),
1518 Value::test_string(path.as_ref().to_str().unwrap()),
1519 );
1520 stack
1521 }
1522
1523 #[test]
1524 fn pwd_not_set() {
1525 let engine_state = EngineState::new();
1526 engine_state.cwd(None).unwrap_err();
1527 }
1528
1529 #[test]
1530 fn pwd_is_empty_string() {
1531 let engine_state = engine_state_with_pwd("");
1532 engine_state.cwd(None).unwrap_err();
1533 }
1534
1535 #[test]
1536 fn pwd_is_non_string_value() {
1537 let mut engine_state = EngineState::new();
1538 engine_state.add_env_var("PWD".into(), Value::test_glob("*"));
1539 engine_state.cwd(None).unwrap_err();
1540 }
1541
1542 #[test]
1543 fn pwd_is_relative_path() {
1544 let engine_state = engine_state_with_pwd("./foo");
1545
1546 engine_state.cwd(None).unwrap_err();
1547 }
1548
1549 #[test]
1550 fn pwd_has_trailing_slash() {
1551 let dir = TempDir::new().unwrap();
1552 let engine_state = engine_state_with_pwd(dir.path().join(""));
1553
1554 engine_state.cwd(None).unwrap_err();
1555 }
1556
1557 #[test]
1558 fn pwd_points_to_root() {
1559 #[cfg(windows)]
1560 let root = Path::new(r"C:\");
1561 #[cfg(not(windows))]
1562 let root = Path::new("/");
1563
1564 let engine_state = engine_state_with_pwd(root);
1565 let cwd = engine_state.cwd(None).unwrap();
1566 assert_path_eq!(cwd, root);
1567 }
1568
1569 #[test]
1570 fn pwd_points_to_normal_file() {
1571 let file = NamedTempFile::new().unwrap();
1572 let engine_state = engine_state_with_pwd(file.path());
1573
1574 engine_state.cwd(None).unwrap_err();
1575 }
1576
1577 #[test]
1578 fn pwd_points_to_normal_directory() {
1579 let dir = TempDir::new().unwrap();
1580 let engine_state = engine_state_with_pwd(dir.path());
1581
1582 let cwd = engine_state.cwd(None).unwrap();
1583 assert_path_eq!(cwd, dir.path());
1584 }
1585
1586 #[test]
1587 fn pwd_points_to_symlink_to_file() {
1588 let file = NamedTempFile::new().unwrap();
1589 let temp_file = AbsolutePath::try_new(file.path()).unwrap();
1590 let dir = TempDir::new().unwrap();
1591 let temp = AbsolutePath::try_new(dir.path()).unwrap();
1592
1593 let link = temp.join("link");
1594 symlink(temp_file, &link).unwrap();
1595 let engine_state = engine_state_with_pwd(&link);
1596
1597 engine_state.cwd(None).unwrap_err();
1598 }
1599
1600 #[test]
1601 fn pwd_points_to_symlink_to_directory() {
1602 let dir = TempDir::new().unwrap();
1603 let temp = AbsolutePath::try_new(dir.path()).unwrap();
1604
1605 let link = temp.join("link");
1606 symlink(temp, &link).unwrap();
1607 let engine_state = engine_state_with_pwd(&link);
1608
1609 let cwd = engine_state.cwd(None).unwrap();
1610 assert_path_eq!(cwd, link);
1611 }
1612
1613 #[test]
1614 fn pwd_points_to_broken_symlink() {
1615 let dir = TempDir::new().unwrap();
1616 let temp = AbsolutePath::try_new(dir.path()).unwrap();
1617 let other_dir = TempDir::new().unwrap();
1618 let other_temp = AbsolutePath::try_new(other_dir.path()).unwrap();
1619
1620 let link = temp.join("link");
1621 symlink(other_temp, &link).unwrap();
1622 let engine_state = engine_state_with_pwd(&link);
1623
1624 drop(other_dir);
1625 engine_state.cwd(None).unwrap_err();
1626 }
1627
1628 #[test]
1629 fn pwd_points_to_nonexistent_entity() {
1630 let engine_state = engine_state_with_pwd(TempDir::new().unwrap().path());
1631
1632 engine_state.cwd(None).unwrap_err();
1633 }
1634
1635 #[test]
1636 fn stack_pwd_not_set() {
1637 let dir = TempDir::new().unwrap();
1638 let engine_state = engine_state_with_pwd(dir.path());
1639 let stack = Stack::new();
1640
1641 let cwd = engine_state.cwd(Some(&stack)).unwrap();
1642 assert_eq!(cwd, dir.path());
1643 }
1644
1645 #[test]
1646 fn stack_pwd_is_empty_string() {
1647 let dir = TempDir::new().unwrap();
1648 let engine_state = engine_state_with_pwd(dir.path());
1649 let stack = stack_with_pwd("");
1650
1651 engine_state.cwd(Some(&stack)).unwrap_err();
1652 }
1653
1654 #[test]
1655 fn stack_pwd_points_to_normal_directory() {
1656 let dir1 = TempDir::new().unwrap();
1657 let dir2 = TempDir::new().unwrap();
1658 let engine_state = engine_state_with_pwd(dir1.path());
1659 let stack = stack_with_pwd(dir2.path());
1660
1661 let cwd = engine_state.cwd(Some(&stack)).unwrap();
1662 assert_path_eq!(cwd, dir2.path());
1663 }
1664
1665 #[test]
1666 fn stack_pwd_points_to_normal_directory_with_symlink_components() {
1667 let dir = TempDir::new().unwrap();
1668 let temp = AbsolutePath::try_new(dir.path()).unwrap();
1669
1670 let link = temp.join("link");
1672 symlink(temp, &link).unwrap();
1673 let foo = link.join("foo");
1674 std::fs::create_dir(temp.join("foo")).unwrap();
1675 let engine_state = EngineState::new();
1676 let stack = stack_with_pwd(&foo);
1677
1678 let cwd = engine_state.cwd(Some(&stack)).unwrap();
1679 assert_path_eq!(cwd, foo);
1680 }
1681}