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,
6 debugger::{Debugger, NoopDebugger},
7 engine::{
8 CachedFile, Command, DEFAULT_OVERLAY_NAME, EnvName, 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 std::{
20 collections::HashMap,
21 num::NonZeroUsize,
22 path::PathBuf,
23 sync::{
24 Arc, Mutex, MutexGuard, PoisonError,
25 atomic::{AtomicBool, AtomicU32, Ordering},
26 mpsc::Sender,
27 mpsc::channel,
28 },
29};
30
31type PoisonDebuggerError<'a> = PoisonError<MutexGuard<'a, Box<dyn Debugger>>>;
32
33#[cfg(feature = "plugin")]
34use crate::{PluginRegistryFile, PluginRegistryItem, RegisteredPlugin};
35
36use super::{CurrentJob, Jobs, Mail, Mailbox, ThreadJob};
37
38#[derive(Clone, Debug)]
39pub enum VirtualPath {
40 File(FileId),
41 Dir(Vec<VirtualPathId>),
42}
43
44pub struct ReplState {
45 pub buffer: String,
46 pub cursor_pos: usize,
48 pub accept: bool,
50}
51
52pub struct IsDebugging(AtomicBool);
53
54impl IsDebugging {
55 pub fn new(val: bool) -> Self {
56 IsDebugging(AtomicBool::new(val))
57 }
58}
59
60impl Clone for IsDebugging {
61 fn clone(&self) -> Self {
62 IsDebugging(AtomicBool::new(self.0.load(Ordering::Relaxed)))
63 }
64}
65
66#[derive(Clone)]
84pub struct EngineState {
85 files: Vec<CachedFile>,
86 pub(super) virtual_paths: Vec<(String, VirtualPath)>,
87 vars: Vec<Variable>,
88 decls: Arc<Vec<Box<dyn Command + 'static>>>,
89 pub(super) blocks: Arc<Vec<Arc<Block>>>,
93 pub(super) modules: Arc<Vec<Arc<Module>>>,
94 pub spans: Vec<Span>,
95 doccomments: Doccomments,
96 pub scope: ScopeFrame,
97 signals: Signals,
98 pub signal_handlers: Option<Handlers>,
99 pub env_vars: Arc<EnvVars>,
100 pub previous_env_vars: Arc<HashMap<String, Value>>,
101 pub config: Arc<Config>,
102 pub pipeline_externals_state: Arc<(AtomicU32, AtomicU32)>,
103 pub repl_state: Arc<Mutex<ReplState>>,
104 pub table_decl_id: Option<DeclId>,
105 #[cfg(feature = "plugin")]
106 pub plugin_path: Option<PathBuf>,
107 #[cfg(feature = "plugin")]
108 plugins: Vec<Arc<dyn RegisteredPlugin>>,
109 config_path: HashMap<String, PathBuf>,
110 pub history_enabled: bool,
111 pub history_session_id: i64,
112 pub file: Option<PathBuf>,
114 pub regex_cache: Arc<Mutex<LruCache<String, Regex>>>,
115 pub is_interactive: bool,
116 pub is_login: bool,
117 pub is_lsp: bool,
118 pub is_mcp: bool,
119 startup_time: i64,
120 is_debugging: IsDebugging,
121 pub debugger: Arc<Mutex<Box<dyn Debugger>>>,
122 pub report_log: Arc<Mutex<ReportLog>>,
123
124 pub jobs: Arc<Mutex<Jobs>>,
125
126 pub current_job: CurrentJob,
128
129 pub root_job_sender: Sender<Mail>,
130
131 pub exit_warning_given: Arc<AtomicBool>,
138}
139
140const REGEX_CACHE_SIZE: usize = 100; pub const NU_VARIABLE_ID: VarId = VarId::new(0);
144pub const IN_VARIABLE_ID: VarId = VarId::new(1);
145pub const ENV_VARIABLE_ID: VarId = VarId::new(2);
146pub const UNKNOWN_SPAN_ID: SpanId = SpanId::new(0);
150
151impl EngineState {
152 pub fn new() -> Self {
153 let (send, recv) = channel::<Mail>();
154
155 Self {
156 files: vec![],
157 virtual_paths: vec![],
158 vars: vec![
159 Variable::new(Span::new(0, 0), Type::Any, false),
160 Variable::new(Span::new(0, 0), Type::Any, false),
161 Variable::new(Span::new(0, 0), Type::Any, false),
162 Variable::new(Span::new(0, 0), Type::Any, false),
163 Variable::new(Span::new(0, 0), Type::Any, false),
164 ],
165 decls: Arc::new(vec![]),
166 blocks: Arc::new(vec![]),
167 modules: Arc::new(vec![Arc::new(Module::new(
168 DEFAULT_OVERLAY_NAME.as_bytes().to_vec(),
169 ))]),
170 spans: vec![Span::unknown()],
171 doccomments: Doccomments::new(),
172 scope: ScopeFrame::with_empty_overlay(
174 DEFAULT_OVERLAY_NAME.as_bytes().to_vec(),
175 ModuleId::new(0),
176 false,
177 ),
178 signal_handlers: None,
179 signals: Signals::empty(),
180 env_vars: Arc::new(
181 [(DEFAULT_OVERLAY_NAME.to_string(), HashMap::new())]
182 .into_iter()
183 .collect(),
184 ),
185 previous_env_vars: Arc::new(HashMap::new()),
186 config: Arc::new(Config::default()),
187 pipeline_externals_state: Arc::new((AtomicU32::new(0), AtomicU32::new(0))),
188 repl_state: Arc::new(Mutex::new(ReplState {
189 buffer: "".to_string(),
190 cursor_pos: 0,
191 accept: false,
192 })),
193 table_decl_id: None,
194 #[cfg(feature = "plugin")]
195 plugin_path: None,
196 #[cfg(feature = "plugin")]
197 plugins: vec![],
198 config_path: HashMap::new(),
199 history_enabled: true,
200 history_session_id: 0,
201 file: None,
202 regex_cache: Arc::new(Mutex::new(LruCache::new(
203 NonZeroUsize::new(REGEX_CACHE_SIZE).expect("tried to create cache of size zero"),
204 ))),
205 is_interactive: false,
206 is_login: false,
207 is_lsp: false,
208 is_mcp: false,
209 startup_time: -1,
210 is_debugging: IsDebugging::new(false),
211 debugger: Arc::new(Mutex::new(Box::new(NoopDebugger))),
212 report_log: Arc::default(),
213 jobs: Arc::new(Mutex::new(Jobs::default())),
214 current_job: CurrentJob {
215 id: JobId::new(0),
216 background_thread_job: None,
217 mailbox: Arc::new(Mutex::new(Mailbox::new(recv))),
218 },
219 root_job_sender: send,
220 exit_warning_given: Arc::new(AtomicBool::new(false)),
221 }
222 }
223
224 pub fn signals(&self) -> &Signals {
225 &self.signals
226 }
227
228 pub fn reset_signals(&mut self) {
229 self.signals.reset();
230 if let Some(ref handlers) = self.signal_handlers {
231 handlers.run(SignalAction::Reset);
232 }
233 }
234
235 pub fn set_signals(&mut self, signals: Signals) {
236 self.signals = signals;
237 }
238
239 pub fn merge_delta(&mut self, mut delta: StateDelta) -> Result<(), ShellError> {
247 self.files.extend(delta.files);
249 self.virtual_paths.extend(delta.virtual_paths);
250 self.vars.extend(delta.vars);
251 self.spans.extend(delta.spans);
252 self.doccomments.merge_with(delta.doccomments);
253
254 if !delta.decls.is_empty() {
256 Arc::make_mut(&mut self.decls).extend(delta.decls);
257 }
258 if !delta.blocks.is_empty() {
259 Arc::make_mut(&mut self.blocks).extend(delta.blocks);
260 }
261 if !delta.modules.is_empty() {
262 Arc::make_mut(&mut self.modules).extend(delta.modules);
263 }
264
265 let first = delta.scope.remove(0);
266
267 for (delta_name, delta_overlay) in first.clone().overlays {
268 if let Some((_, existing_overlay)) = self
269 .scope
270 .overlays
271 .iter_mut()
272 .find(|(name, _)| name == &delta_name)
273 {
274 for item in delta_overlay.decls.into_iter() {
276 existing_overlay.decls.insert(item.0, item.1);
277 }
278 for item in delta_overlay.vars.into_iter() {
279 existing_overlay.insert_variable(item.0, item.1);
280 }
281 for item in delta_overlay.modules.into_iter() {
282 existing_overlay.modules.insert(item.0, item.1);
283 }
284
285 existing_overlay
286 .visibility
287 .merge_with(delta_overlay.visibility);
288 } else {
289 self.scope.overlays.push((delta_name, delta_overlay));
291 }
292 }
293
294 let mut activated_ids = self.translate_overlay_ids(&first);
295
296 let mut removed_ids = vec![];
297
298 for name in &first.removed_overlays {
299 if let Some(overlay_id) = self.find_overlay(name) {
300 removed_ids.push(overlay_id);
301 }
302 }
303
304 self.scope
306 .active_overlays
307 .retain(|id| !removed_ids.contains(id));
308
309 self.scope
311 .active_overlays
312 .retain(|id| !activated_ids.contains(id));
313 self.scope.active_overlays.append(&mut activated_ids);
314
315 #[cfg(feature = "plugin")]
316 if !delta.plugins.is_empty() {
317 for plugin in std::mem::take(&mut delta.plugins) {
318 if let Some(handlers) = &self.signal_handlers {
320 plugin.clone().configure_signal_handler(handlers)?;
321 }
322
323 if let Some(existing) = self
325 .plugins
326 .iter_mut()
327 .find(|p| p.identity().name() == plugin.identity().name())
328 {
329 existing.stop()?;
331 *existing = plugin;
332 } else {
333 self.plugins.push(plugin);
334 }
335 }
336 }
337
338 #[cfg(feature = "plugin")]
339 if !delta.plugin_registry_items.is_empty() {
340 if self.plugin_path.is_some() {
342 self.update_plugin_file(std::mem::take(&mut delta.plugin_registry_items))?;
343 }
344 }
345
346 Ok(())
347 }
348
349 pub fn merge_env(&mut self, stack: &mut Stack) -> Result<(), ShellError> {
351 for mut scope in stack.env_vars.drain(..) {
352 for (overlay_name, mut env) in Arc::make_mut(&mut scope).drain() {
353 if let Some(env_vars) = Arc::make_mut(&mut self.env_vars).get_mut(&overlay_name) {
354 env_vars.extend(env.drain());
356 } else {
357 Arc::make_mut(&mut self.env_vars).insert(overlay_name, env);
359 }
360 }
361 }
362
363 let cwd = self.cwd(Some(stack))?;
364 std::env::set_current_dir(cwd).map_err(|err| {
365 IoError::new_internal(err, "Could not set current dir", crate::location!())
366 })?;
367
368 if let Some(config) = stack.config.take() {
369 self.config = config;
371
372 #[cfg(feature = "plugin")]
374 self.update_plugin_gc_configs(&self.config.plugin_gc);
375 }
376
377 Ok(())
378 }
379
380 pub fn cleanup_stack_variables(&mut self, stack: &mut Stack) {
383 use std::collections::HashSet;
384
385 let mut shadowed_vars = HashSet::new();
386 for (_, frame) in self.scope.overlays.iter_mut() {
387 shadowed_vars.extend(frame.shadowed_vars.to_owned());
388 frame.shadowed_vars.clear();
389 }
390
391 stack
393 .vars
394 .retain(|(var_id, _)| !shadowed_vars.contains(var_id));
395 }
396
397 pub fn active_overlay_ids<'a, 'b>(
398 &'b self,
399 removed_overlays: &'a [Vec<u8>],
400 ) -> impl DoubleEndedIterator<Item = &'b OverlayId> + 'a
401 where
402 'b: 'a,
403 {
404 self.scope.active_overlays.iter().filter(|id| {
405 !removed_overlays
406 .iter()
407 .any(|name| name == self.get_overlay_name(**id))
408 })
409 }
410
411 pub fn active_overlays<'a, 'b>(
412 &'b self,
413 removed_overlays: &'a [Vec<u8>],
414 ) -> impl DoubleEndedIterator<Item = &'b OverlayFrame> + 'a
415 where
416 'b: 'a,
417 {
418 self.active_overlay_ids(removed_overlays)
419 .map(|id| self.get_overlay(*id))
420 }
421
422 pub fn active_overlay_names<'a, 'b>(
423 &'b self,
424 removed_overlays: &'a [Vec<u8>],
425 ) -> impl DoubleEndedIterator<Item = &'b [u8]> + 'a
426 where
427 'b: 'a,
428 {
429 self.active_overlay_ids(removed_overlays)
430 .map(|id| self.get_overlay_name(*id))
431 }
432
433 fn translate_overlay_ids(&self, other: &ScopeFrame) -> Vec<OverlayId> {
435 let other_names = other.active_overlays.iter().map(|other_id| {
436 &other
437 .overlays
438 .get(other_id.get())
439 .expect("internal error: missing overlay")
440 .0
441 });
442
443 other_names
444 .map(|other_name| {
445 self.find_overlay(other_name)
446 .expect("internal error: missing overlay")
447 })
448 .collect()
449 }
450
451 pub fn last_overlay_name(&self, removed_overlays: &[Vec<u8>]) -> &[u8] {
452 self.active_overlay_names(removed_overlays)
453 .last()
454 .expect("internal error: no active overlays")
455 }
456
457 pub fn last_overlay(&self, removed_overlays: &[Vec<u8>]) -> &OverlayFrame {
458 self.active_overlay_ids(removed_overlays)
459 .last()
460 .map(|id| self.get_overlay(*id))
461 .expect("internal error: no active overlays")
462 }
463
464 pub fn get_overlay_name(&self, overlay_id: OverlayId) -> &[u8] {
465 &self
466 .scope
467 .overlays
468 .get(overlay_id.get())
469 .expect("internal error: missing overlay")
470 .0
471 }
472
473 pub fn get_overlay(&self, overlay_id: OverlayId) -> &OverlayFrame {
474 &self
475 .scope
476 .overlays
477 .get(overlay_id.get())
478 .expect("internal error: missing overlay")
479 .1
480 }
481
482 pub fn render_env_vars(&self) -> HashMap<&str, &Value> {
483 let mut result: HashMap<&str, &Value> = HashMap::new();
484
485 for overlay_name in self.active_overlay_names(&[]) {
486 let name = String::from_utf8_lossy(overlay_name);
487 if let Some(env_vars) = self.env_vars.get(name.as_ref()) {
488 result.extend(env_vars.iter().map(|(k, v)| (k.as_str(), v)));
489 }
490 }
491
492 result
493 }
494
495 pub fn add_env_var(&mut self, name: String, val: Value) {
496 let overlay_name = String::from_utf8_lossy(self.last_overlay_name(&[])).to_string();
497
498 if let Some(env_vars) = Arc::make_mut(&mut self.env_vars).get_mut(&overlay_name) {
499 env_vars.insert(EnvName::from(name), val);
500 } else {
501 Arc::make_mut(&mut self.env_vars).insert(
502 overlay_name,
503 [(EnvName::from(name), val)].into_iter().collect(),
504 );
505 }
506 }
507
508 pub fn get_env_var(&self, name: &str) -> Option<&Value> {
509 for overlay_id in self.scope.active_overlays.iter().rev() {
510 let overlay_name = String::from_utf8_lossy(self.get_overlay_name(*overlay_id));
511 if let Some(env_vars) = self.env_vars.get(overlay_name.as_ref())
512 && let Some(val) = env_vars.get(&EnvName::from(name))
513 {
514 return Some(val);
515 }
516 }
517
518 None
519 }
520
521 #[cfg(feature = "plugin")]
522 pub fn plugins(&self) -> &[Arc<dyn RegisteredPlugin>] {
523 &self.plugins
524 }
525
526 #[cfg(feature = "plugin")]
527 fn update_plugin_file(&self, updated_items: Vec<PluginRegistryItem>) -> Result<(), ShellError> {
528 use std::fs::File;
530
531 let plugin_path = self
532 .plugin_path
533 .as_ref()
534 .ok_or_else(|| ShellError::GenericError {
535 error: "Plugin file path not set".into(),
536 msg: "".into(),
537 span: None,
538 help: Some("you may be running nu with --no-config-file".into()),
539 inner: vec![],
540 })?;
541
542 let mut contents = match File::open(plugin_path.as_path()) {
544 Ok(mut plugin_file) => PluginRegistryFile::read_from(&mut plugin_file, None),
545 Err(err) => {
546 if err.kind() == std::io::ErrorKind::NotFound {
547 Ok(PluginRegistryFile::default())
548 } else {
549 Err(ShellError::Io(IoError::new_internal_with_path(
550 err,
551 "Failed to open plugin file",
552 crate::location!(),
553 PathBuf::from(plugin_path),
554 )))
555 }
556 }
557 }?;
558
559 for item in updated_items {
561 contents.upsert_plugin(item);
562 }
563
564 let plugin_file = File::create(plugin_path.as_path()).map_err(|err| {
566 IoError::new_internal_with_path(
567 err,
568 "Failed to write plugin file",
569 crate::location!(),
570 PathBuf::from(plugin_path),
571 )
572 })?;
573
574 contents.write_to(plugin_file, None)
575 }
576
577 #[cfg(feature = "plugin")]
579 fn update_plugin_gc_configs(&self, plugin_gc: &crate::PluginGcConfigs) {
580 for plugin in &self.plugins {
581 plugin.set_gc_config(plugin_gc.get(plugin.identity().name()));
582 }
583 }
584
585 pub fn num_files(&self) -> usize {
586 self.files.len()
587 }
588
589 pub fn num_virtual_paths(&self) -> usize {
590 self.virtual_paths.len()
591 }
592
593 pub fn num_vars(&self) -> usize {
594 self.vars.len()
595 }
596
597 pub fn num_decls(&self) -> usize {
598 self.decls.len()
599 }
600
601 pub fn num_blocks(&self) -> usize {
602 self.blocks.len()
603 }
604
605 pub fn num_modules(&self) -> usize {
606 self.modules.len()
607 }
608
609 pub fn num_spans(&self) -> usize {
610 self.spans.len()
611 }
612 pub fn print_vars(&self) {
613 for var in self.vars.iter().enumerate() {
614 println!("var{}: {:?}", var.0, var.1);
615 }
616 }
617
618 pub fn print_decls(&self) {
619 for decl in self.decls.iter().enumerate() {
620 println!("decl{}: {:?}", decl.0, decl.1.signature());
621 }
622 }
623
624 pub fn print_blocks(&self) {
625 for block in self.blocks.iter().enumerate() {
626 println!("block{}: {:?}", block.0, block.1);
627 }
628 }
629
630 pub fn print_contents(&self) {
631 for cached_file in self.files.iter() {
632 let string = String::from_utf8_lossy(&cached_file.content);
633 println!("{string}");
634 }
635 }
636
637 pub fn find_decl(&self, name: &[u8], removed_overlays: &[Vec<u8>]) -> Option<DeclId> {
641 let mut visibility: Visibility = Visibility::new();
642
643 for overlay_frame in self.active_overlays(removed_overlays).rev() {
644 visibility.append(&overlay_frame.visibility);
645
646 if let Some(decl_id) = overlay_frame.get_decl(name)
647 && visibility.is_decl_id_visible(&decl_id)
648 {
649 return Some(decl_id);
650 }
651 }
652
653 None
654 }
655
656 pub fn find_decl_name(&self, decl_id: DeclId, removed_overlays: &[Vec<u8>]) -> Option<&[u8]> {
660 let mut visibility: Visibility = Visibility::new();
661
662 for overlay_frame in self.active_overlays(removed_overlays).rev() {
663 visibility.append(&overlay_frame.visibility);
664
665 if visibility.is_decl_id_visible(&decl_id) {
666 for (name, id) in overlay_frame.decls.iter() {
667 if id == &decl_id {
668 return Some(name);
669 }
670 }
671 }
672 }
673
674 None
675 }
676
677 pub fn find_overlay(&self, name: &[u8]) -> Option<OverlayId> {
681 self.scope.find_overlay(name)
682 }
683
684 pub fn find_active_overlay(&self, name: &[u8]) -> Option<OverlayId> {
688 self.scope.find_active_overlay(name)
689 }
690
691 pub fn find_module(&self, name: &[u8], removed_overlays: &[Vec<u8>]) -> Option<ModuleId> {
695 for overlay_frame in self.active_overlays(removed_overlays).rev() {
696 if let Some(module_id) = overlay_frame.modules.get(name) {
697 return Some(*module_id);
698 }
699 }
700
701 None
702 }
703
704 pub fn get_module_comments(&self, module_id: ModuleId) -> Option<&[Span]> {
705 self.doccomments.get_module_comments(module_id)
706 }
707
708 #[cfg(feature = "plugin")]
709 pub fn plugin_decls(&self) -> impl Iterator<Item = &Box<dyn Command + 'static>> {
710 let mut unique_plugin_decls = HashMap::new();
711
712 for decl in self.decls.iter().filter(|d| d.is_plugin()) {
714 unique_plugin_decls.insert(decl.name(), decl);
715 }
716
717 let mut plugin_decls: Vec<(&str, &Box<dyn Command>)> =
718 unique_plugin_decls.into_iter().collect();
719
720 plugin_decls.sort_by(|a, b| a.0.cmp(b.0));
722 plugin_decls.into_iter().map(|(_, decl)| decl)
723 }
724
725 pub fn which_module_has_decl(
726 &self,
727 decl_name: &[u8],
728 removed_overlays: &[Vec<u8>],
729 ) -> Option<&[u8]> {
730 for overlay_frame in self.active_overlays(removed_overlays).rev() {
731 for (module_name, module_id) in overlay_frame.modules.iter() {
732 let module = self.get_module(*module_id);
733 if module.has_decl(decl_name) {
734 return Some(module_name);
735 }
736 }
737 }
738
739 None
740 }
741
742 pub fn traverse_commands(&self, mut f: impl FnMut(&[u8], DeclId)) {
744 for overlay_frame in self.active_overlays(&[]).rev() {
745 for (name, decl_id) in &overlay_frame.decls {
746 if overlay_frame.visibility.is_decl_id_visible(decl_id) {
747 f(name, *decl_id);
748 }
749 }
750 }
751 }
752
753 pub fn get_span_contents(&self, span: Span) -> &[u8] {
754 self.try_get_file_contents(span).unwrap_or(&[0u8; 0])
755 }
756
757 pub fn try_get_file_contents(&self, span: Span) -> Option<&[u8]> {
758 self.files.iter().find_map(|file| {
759 if file.covered_span.contains_span(span) {
760 let start = span.start - file.covered_span.start;
761 let end = span.end - file.covered_span.start;
762 Some(&file.content[start..end])
763 } else {
764 None
765 }
766 })
767 }
768
769 pub fn span_match_prefix(&self, span: Span, prefix: &[u8]) -> Option<(Span, Span)> {
772 let contents = self.get_span_contents(span);
773
774 if contents.starts_with(prefix) {
775 span.split_at(prefix.len())
776 } else {
777 None
778 }
779 }
780
781 pub fn span_match_postfix(&self, span: Span, prefix: &[u8]) -> Option<(Span, Span)> {
784 let contents = self.get_span_contents(span);
785
786 if contents.ends_with(prefix) {
787 span.split_at(span.len() - prefix.len())
788 } else {
789 None
790 }
791 }
792
793 pub fn get_config(&self) -> &Arc<Config> {
798 &self.config
799 }
800
801 pub fn set_config(&mut self, conf: impl Into<Arc<Config>>) {
802 let conf = conf.into();
803
804 #[cfg(feature = "plugin")]
805 if conf.plugin_gc != self.config.plugin_gc {
806 self.update_plugin_gc_configs(&conf.plugin_gc);
808 }
809
810 self.config = conf;
811 }
812
813 pub fn get_plugin_config(&self, plugin: &str) -> Option<&Value> {
818 self.config.plugins.get(plugin)
819 }
820
821 pub fn history_config(&self) -> Option<HistoryConfig> {
823 self.history_enabled.then(|| self.config.history)
824 }
825
826 pub fn get_var(&self, var_id: VarId) -> &Variable {
827 self.vars
828 .get(var_id.get())
829 .expect("internal error: missing variable")
830 }
831
832 pub fn get_constant(&self, var_id: VarId) -> Option<&Value> {
833 let var = self.get_var(var_id);
834 var.const_val.as_ref()
835 }
836
837 pub fn generate_nu_constant(&mut self) {
838 self.vars[NU_VARIABLE_ID.get()].const_val = Some(create_nu_constant(self, Span::unknown()));
839 }
840
841 pub fn get_decl(&self, decl_id: DeclId) -> &dyn Command {
842 self.decls
843 .get(decl_id.get())
844 .expect("internal error: missing declaration")
845 .as_ref()
846 }
847
848 pub fn get_decls_sorted(&self, include_hidden: bool) -> Vec<(Vec<u8>, DeclId)> {
850 let mut decls_map = HashMap::new();
851
852 for overlay_frame in self.active_overlays(&[]) {
853 let new_decls = if include_hidden {
854 overlay_frame.decls.clone()
855 } else {
856 overlay_frame
857 .decls
858 .clone()
859 .into_iter()
860 .filter(|(_, id)| overlay_frame.visibility.is_decl_id_visible(id))
861 .collect()
862 };
863
864 decls_map.extend(new_decls);
865 }
866
867 let mut decls: Vec<(Vec<u8>, DeclId)> = decls_map.into_iter().collect();
868
869 decls.sort_by(|a, b| a.0.cmp(&b.0));
870 decls
871 }
872
873 pub fn get_signature(&self, decl: &dyn Command) -> Signature {
874 if let Some(block_id) = decl.block_id() {
875 *self.blocks[block_id.get()].signature.clone()
876 } else {
877 decl.signature()
878 }
879 }
880
881 pub fn get_signatures_and_declids(&self, include_hidden: bool) -> Vec<(Signature, DeclId)> {
883 self.get_decls_sorted(include_hidden)
884 .into_iter()
885 .map(|(_, id)| {
886 let decl = self.get_decl(id);
887
888 (self.get_signature(decl).update_from_command(decl), id)
889 })
890 .collect()
891 }
892
893 pub fn get_block(&self, block_id: BlockId) -> &Arc<Block> {
894 self.blocks
895 .get(block_id.get())
896 .expect("internal error: missing block")
897 }
898
899 pub fn try_get_block(&self, block_id: BlockId) -> Option<&Arc<Block>> {
905 self.blocks.get(block_id.get())
906 }
907
908 pub fn get_module(&self, module_id: ModuleId) -> &Module {
909 self.modules
910 .get(module_id.get())
911 .expect("internal error: missing module")
912 }
913
914 pub fn get_virtual_path(&self, virtual_path_id: VirtualPathId) -> &(String, VirtualPath) {
915 self.virtual_paths
916 .get(virtual_path_id.get())
917 .expect("internal error: missing virtual path")
918 }
919
920 pub fn next_span_start(&self) -> usize {
921 if let Some(cached_file) = self.files.last() {
922 cached_file.covered_span.end
923 } else {
924 0
925 }
926 }
927
928 pub fn files(
929 &self,
930 ) -> impl DoubleEndedIterator<Item = &CachedFile> + ExactSizeIterator<Item = &CachedFile> {
931 self.files.iter()
932 }
933
934 pub fn add_file(&mut self, filename: Arc<str>, content: Arc<[u8]>) -> FileId {
935 let next_span_start = self.next_span_start();
936 let next_span_end = next_span_start + content.len();
937
938 let covered_span = Span::new(next_span_start, next_span_end);
939
940 self.files.push(CachedFile {
941 name: filename,
942 content,
943 covered_span,
944 });
945
946 FileId::new(self.num_files() - 1)
947 }
948
949 pub fn set_config_path(&mut self, key: &str, val: PathBuf) {
950 self.config_path.insert(key.to_string(), val);
951 }
952
953 pub fn get_config_path(&self, key: &str) -> Option<&PathBuf> {
954 self.config_path.get(key)
955 }
956
957 pub fn build_desc(&self, spans: &[Span]) -> (String, String) {
958 let comment_lines: Vec<&[u8]> = spans
959 .iter()
960 .map(|span| self.get_span_contents(*span))
961 .collect();
962 build_desc(&comment_lines)
963 }
964
965 pub fn build_module_desc(&self, module_id: ModuleId) -> Option<(String, String)> {
966 self.get_module_comments(module_id)
967 .map(|comment_spans| self.build_desc(comment_spans))
968 }
969
970 #[deprecated(since = "0.92.3", note = "please use `EngineState::cwd()` instead")]
974 pub fn current_work_dir(&self) -> String {
975 self.cwd(None)
976 .map(|path| path.to_string_lossy().to_string())
977 .unwrap_or_default()
978 }
979
980 pub fn cwd(&self, stack: Option<&Stack>) -> Result<AbsolutePathBuf, ShellError> {
987 fn error(msg: &str, cwd: impl AsRef<nu_path::Path>) -> ShellError {
989 ShellError::GenericError {
990 error: msg.into(),
991 msg: format!("$env.PWD = {}", cwd.as_ref().display()),
992 span: None,
993 help: Some("Use `cd` to reset $env.PWD into a good state".into()),
994 inner: vec![],
995 }
996 }
997
998 let pwd = if let Some(stack) = stack {
1000 stack.get_env_var(self, "PWD")
1001 } else {
1002 self.get_env_var("PWD")
1003 };
1004
1005 let pwd = pwd.ok_or_else(|| error("$env.PWD not found", ""))?;
1006
1007 if let Ok(pwd) = pwd.as_str() {
1008 let path = AbsolutePathBuf::try_from(pwd)
1009 .map_err(|_| error("$env.PWD is not an absolute path", pwd))?;
1010
1011 if path.parent().is_some() && nu_path::has_trailing_slash(path.as_ref()) {
1014 Err(error("$env.PWD contains trailing slashes", &path))
1015 } else if !path.exists() {
1016 Err(error("$env.PWD points to a non-existent directory", &path))
1017 } else if !path.is_dir() {
1018 Err(error("$env.PWD points to a non-directory", &path))
1019 } else {
1020 Ok(path)
1021 }
1022 } else {
1023 Err(error("$env.PWD is not a string", format!("{pwd:?}")))
1024 }
1025 }
1026
1027 pub fn cwd_as_string(&self, stack: Option<&Stack>) -> Result<String, ShellError> {
1029 let cwd = self.cwd(stack)?;
1030 cwd.into_os_string()
1031 .into_string()
1032 .map_err(|err| ShellError::NonUtf8Custom {
1033 msg: format!("The current working directory is not a valid utf-8 string: {err:?}"),
1034 span: Span::unknown(),
1035 })
1036 }
1037
1038 pub fn get_file_contents(&self) -> &[CachedFile] {
1040 &self.files
1041 }
1042
1043 pub fn get_startup_time(&self) -> i64 {
1044 self.startup_time
1045 }
1046
1047 pub fn set_startup_time(&mut self, startup_time: i64) {
1048 self.startup_time = startup_time;
1049 }
1050
1051 pub fn activate_debugger(
1052 &self,
1053 debugger: Box<dyn Debugger>,
1054 ) -> Result<(), PoisonDebuggerError<'_>> {
1055 let mut locked_debugger = self.debugger.lock()?;
1056 *locked_debugger = debugger;
1057 locked_debugger.activate();
1058 self.is_debugging.0.store(true, Ordering::Relaxed);
1059 Ok(())
1060 }
1061
1062 pub fn deactivate_debugger(&self) -> Result<Box<dyn Debugger>, PoisonDebuggerError<'_>> {
1063 let mut locked_debugger = self.debugger.lock()?;
1064 locked_debugger.deactivate();
1065 let ret = std::mem::replace(&mut *locked_debugger, Box::new(NoopDebugger));
1066 self.is_debugging.0.store(false, Ordering::Relaxed);
1067 Ok(ret)
1068 }
1069
1070 pub fn is_debugging(&self) -> bool {
1071 self.is_debugging.0.load(Ordering::Relaxed)
1072 }
1073
1074 pub fn recover_from_panic(&mut self) {
1075 if Mutex::is_poisoned(&self.repl_state) {
1076 self.repl_state = Arc::new(Mutex::new(ReplState {
1077 buffer: "".to_string(),
1078 cursor_pos: 0,
1079 accept: false,
1080 }));
1081 }
1082 if Mutex::is_poisoned(&self.jobs) {
1083 self.jobs = Arc::new(Mutex::new(Jobs::default()));
1084 }
1085 if Mutex::is_poisoned(&self.regex_cache) {
1086 self.regex_cache = Arc::new(Mutex::new(LruCache::new(
1087 NonZeroUsize::new(REGEX_CACHE_SIZE).expect("tried to create cache of size zero"),
1088 )));
1089 }
1090 }
1091
1092 pub fn add_span(&mut self, span: Span) -> SpanId {
1094 self.spans.push(span);
1095 SpanId::new(self.num_spans() - 1)
1096 }
1097
1098 pub fn find_span_id(&self, span: Span) -> Option<SpanId> {
1100 self.spans
1101 .iter()
1102 .position(|sp| sp == &span)
1103 .map(SpanId::new)
1104 }
1105
1106 pub fn is_background_job(&self) -> bool {
1108 self.current_job.background_thread_job.is_some()
1109 }
1110
1111 pub fn current_thread_job(&self) -> Option<&ThreadJob> {
1113 self.current_job.background_thread_job.as_ref()
1114 }
1115}
1116
1117impl GetSpan for &EngineState {
1118 fn get_span(&self, span_id: SpanId) -> Span {
1120 *self
1121 .spans
1122 .get(span_id.get())
1123 .expect("internal error: missing span")
1124 }
1125}
1126
1127impl Default for EngineState {
1128 fn default() -> Self {
1129 Self::new()
1130 }
1131}
1132
1133#[cfg(test)]
1134mod engine_state_tests {
1135 use crate::engine::StateWorkingSet;
1136 use std::str::{Utf8Error, from_utf8};
1137
1138 use super::*;
1139
1140 #[test]
1141 fn add_file_gives_id() {
1142 let engine_state = EngineState::new();
1143 let mut engine_state = StateWorkingSet::new(&engine_state);
1144 let id = engine_state.add_file("test.nu".into(), &[]);
1145
1146 assert_eq!(id, FileId::new(0));
1147 }
1148
1149 #[test]
1150 fn add_file_gives_id_including_parent() {
1151 let mut engine_state = EngineState::new();
1152 let parent_id = engine_state.add_file("test.nu".into(), Arc::new([]));
1153
1154 let mut working_set = StateWorkingSet::new(&engine_state);
1155 let working_set_id = working_set.add_file("child.nu".into(), &[]);
1156
1157 assert_eq!(parent_id, FileId::new(0));
1158 assert_eq!(working_set_id, FileId::new(1));
1159 }
1160
1161 #[test]
1162 fn merge_states() -> Result<(), ShellError> {
1163 let mut engine_state = EngineState::new();
1164 engine_state.add_file("test.nu".into(), Arc::new([]));
1165
1166 let delta = {
1167 let mut working_set = StateWorkingSet::new(&engine_state);
1168 let _ = working_set.add_file("child.nu".into(), &[]);
1169 working_set.render()
1170 };
1171
1172 engine_state.merge_delta(delta)?;
1173
1174 assert_eq!(engine_state.num_files(), 2);
1175 assert_eq!(&*engine_state.files[0].name, "test.nu");
1176 assert_eq!(&*engine_state.files[1].name, "child.nu");
1177
1178 Ok(())
1179 }
1180
1181 #[test]
1182 fn list_variables() -> Result<(), Utf8Error> {
1183 let varname = "something";
1184 let varname_with_sigil = "$".to_owned() + varname;
1185 let engine_state = EngineState::new();
1186 let mut working_set = StateWorkingSet::new(&engine_state);
1187 working_set.add_variable(
1188 varname.as_bytes().into(),
1189 Span { start: 0, end: 1 },
1190 Type::Int,
1191 false,
1192 );
1193 let variables = working_set
1194 .list_variables()
1195 .into_iter()
1196 .map(from_utf8)
1197 .collect::<Result<Vec<&str>, Utf8Error>>()?;
1198 assert_eq!(variables, vec![varname_with_sigil]);
1199 Ok(())
1200 }
1201
1202 #[test]
1203 fn get_plugin_config() {
1204 let mut engine_state = EngineState::new();
1205
1206 assert!(
1207 engine_state.get_plugin_config("example").is_none(),
1208 "Unexpected plugin configuration"
1209 );
1210
1211 let mut plugins = HashMap::new();
1212 plugins.insert("example".into(), Value::string("value", Span::test_data()));
1213
1214 let mut config = Config::clone(engine_state.get_config());
1215 config.plugins = plugins;
1216
1217 engine_state.set_config(config);
1218
1219 assert!(
1220 engine_state.get_plugin_config("example").is_some(),
1221 "Plugin configuration not found"
1222 );
1223 }
1224}
1225
1226#[cfg(test)]
1227mod test_cwd {
1228 use crate::{
1243 Value,
1244 engine::{EngineState, Stack},
1245 };
1246 use nu_path::{AbsolutePath, Path, assert_path_eq};
1247 use tempfile::{NamedTempFile, TempDir};
1248
1249 #[cfg(any(unix, windows))]
1251 fn symlink(
1252 original: impl AsRef<AbsolutePath>,
1253 link: impl AsRef<AbsolutePath>,
1254 ) -> std::io::Result<()> {
1255 let original = original.as_ref();
1256 let link = link.as_ref();
1257
1258 #[cfg(unix)]
1259 {
1260 std::os::unix::fs::symlink(original, link)
1261 }
1262 #[cfg(windows)]
1263 {
1264 if original.is_dir() {
1265 std::os::windows::fs::symlink_dir(original, link)
1266 } else {
1267 std::os::windows::fs::symlink_file(original, link)
1268 }
1269 }
1270 }
1271
1272 fn engine_state_with_pwd(path: impl AsRef<Path>) -> EngineState {
1274 let mut engine_state = EngineState::new();
1275 engine_state.add_env_var(
1276 "PWD".into(),
1277 Value::test_string(path.as_ref().to_str().unwrap()),
1278 );
1279 engine_state
1280 }
1281
1282 fn stack_with_pwd(path: impl AsRef<Path>) -> Stack {
1284 let mut stack = Stack::new();
1285 stack.add_env_var(
1286 "PWD".into(),
1287 Value::test_string(path.as_ref().to_str().unwrap()),
1288 );
1289 stack
1290 }
1291
1292 #[test]
1293 fn pwd_not_set() {
1294 let engine_state = EngineState::new();
1295 engine_state.cwd(None).unwrap_err();
1296 }
1297
1298 #[test]
1299 fn pwd_is_empty_string() {
1300 let engine_state = engine_state_with_pwd("");
1301 engine_state.cwd(None).unwrap_err();
1302 }
1303
1304 #[test]
1305 fn pwd_is_non_string_value() {
1306 let mut engine_state = EngineState::new();
1307 engine_state.add_env_var("PWD".into(), Value::test_glob("*"));
1308 engine_state.cwd(None).unwrap_err();
1309 }
1310
1311 #[test]
1312 fn pwd_is_relative_path() {
1313 let engine_state = engine_state_with_pwd("./foo");
1314
1315 engine_state.cwd(None).unwrap_err();
1316 }
1317
1318 #[test]
1319 fn pwd_has_trailing_slash() {
1320 let dir = TempDir::new().unwrap();
1321 let engine_state = engine_state_with_pwd(dir.path().join(""));
1322
1323 engine_state.cwd(None).unwrap_err();
1324 }
1325
1326 #[test]
1327 fn pwd_points_to_root() {
1328 #[cfg(windows)]
1329 let root = Path::new(r"C:\");
1330 #[cfg(not(windows))]
1331 let root = Path::new("/");
1332
1333 let engine_state = engine_state_with_pwd(root);
1334 let cwd = engine_state.cwd(None).unwrap();
1335 assert_path_eq!(cwd, root);
1336 }
1337
1338 #[test]
1339 fn pwd_points_to_normal_file() {
1340 let file = NamedTempFile::new().unwrap();
1341 let engine_state = engine_state_with_pwd(file.path());
1342
1343 engine_state.cwd(None).unwrap_err();
1344 }
1345
1346 #[test]
1347 fn pwd_points_to_normal_directory() {
1348 let dir = TempDir::new().unwrap();
1349 let engine_state = engine_state_with_pwd(dir.path());
1350
1351 let cwd = engine_state.cwd(None).unwrap();
1352 assert_path_eq!(cwd, dir.path());
1353 }
1354
1355 #[test]
1356 fn pwd_points_to_symlink_to_file() {
1357 let file = NamedTempFile::new().unwrap();
1358 let temp_file = AbsolutePath::try_new(file.path()).unwrap();
1359 let dir = TempDir::new().unwrap();
1360 let temp = AbsolutePath::try_new(dir.path()).unwrap();
1361
1362 let link = temp.join("link");
1363 symlink(temp_file, &link).unwrap();
1364 let engine_state = engine_state_with_pwd(&link);
1365
1366 engine_state.cwd(None).unwrap_err();
1367 }
1368
1369 #[test]
1370 fn pwd_points_to_symlink_to_directory() {
1371 let dir = TempDir::new().unwrap();
1372 let temp = AbsolutePath::try_new(dir.path()).unwrap();
1373
1374 let link = temp.join("link");
1375 symlink(temp, &link).unwrap();
1376 let engine_state = engine_state_with_pwd(&link);
1377
1378 let cwd = engine_state.cwd(None).unwrap();
1379 assert_path_eq!(cwd, link);
1380 }
1381
1382 #[test]
1383 fn pwd_points_to_broken_symlink() {
1384 let dir = TempDir::new().unwrap();
1385 let temp = AbsolutePath::try_new(dir.path()).unwrap();
1386 let other_dir = TempDir::new().unwrap();
1387 let other_temp = AbsolutePath::try_new(other_dir.path()).unwrap();
1388
1389 let link = temp.join("link");
1390 symlink(other_temp, &link).unwrap();
1391 let engine_state = engine_state_with_pwd(&link);
1392
1393 drop(other_dir);
1394 engine_state.cwd(None).unwrap_err();
1395 }
1396
1397 #[test]
1398 fn pwd_points_to_nonexistent_entity() {
1399 let engine_state = engine_state_with_pwd(TempDir::new().unwrap().path());
1400
1401 engine_state.cwd(None).unwrap_err();
1402 }
1403
1404 #[test]
1405 fn stack_pwd_not_set() {
1406 let dir = TempDir::new().unwrap();
1407 let engine_state = engine_state_with_pwd(dir.path());
1408 let stack = Stack::new();
1409
1410 let cwd = engine_state.cwd(Some(&stack)).unwrap();
1411 assert_eq!(cwd, dir.path());
1412 }
1413
1414 #[test]
1415 fn stack_pwd_is_empty_string() {
1416 let dir = TempDir::new().unwrap();
1417 let engine_state = engine_state_with_pwd(dir.path());
1418 let stack = stack_with_pwd("");
1419
1420 engine_state.cwd(Some(&stack)).unwrap_err();
1421 }
1422
1423 #[test]
1424 fn stack_pwd_points_to_normal_directory() {
1425 let dir1 = TempDir::new().unwrap();
1426 let dir2 = TempDir::new().unwrap();
1427 let engine_state = engine_state_with_pwd(dir1.path());
1428 let stack = stack_with_pwd(dir2.path());
1429
1430 let cwd = engine_state.cwd(Some(&stack)).unwrap();
1431 assert_path_eq!(cwd, dir2.path());
1432 }
1433
1434 #[test]
1435 fn stack_pwd_points_to_normal_directory_with_symlink_components() {
1436 let dir = TempDir::new().unwrap();
1437 let temp = AbsolutePath::try_new(dir.path()).unwrap();
1438
1439 let link = temp.join("link");
1441 symlink(temp, &link).unwrap();
1442 let foo = link.join("foo");
1443 std::fs::create_dir(temp.join("foo")).unwrap();
1444 let engine_state = EngineState::new();
1445 let stack = stack_with_pwd(&foo);
1446
1447 let cwd = engine_state.cwd(Some(&stack)).unwrap();
1448 assert_path_eq!(cwd, foo);
1449 }
1450}