1use crate::{
2 Config, ENV_VARIABLE_ID, IntoValue, LAST_VARIABLE_ID, NU_VARIABLE_ID, OutDest, PipelineData,
3 PipelineMetadata, ShellError, Span, Value, VarId,
4 ast::PathMember,
5 engine::{
6 ArgumentStack, DEFAULT_OVERLAY_NAME, EngineState, EnvName, ErrorHandlerStack, Redirection,
7 ScopeBindings, StackCallArgGuard, StackCollectValueGuard, StackIoGuard, StackOutDest,
8 StackWithInvocation,
9 },
10 ir::ScopeRegion,
11 record, report_shell_warning,
12 shell_error::generic::GenericError,
13 truncate_value_to_budget,
14};
15use std::{
16 collections::{HashMap, HashSet},
17 fs::File,
18 path::{Component, MAIN_SEPARATOR},
19 sync::{Arc, Mutex},
20};
21
22#[derive(Debug, Default)]
32struct LastResultSlot {
33 present: bool,
35 last: Option<Value>,
37 metadata: Option<PipelineMetadata>,
39 truncated: bool,
40 exit_code: i64,
42 duration_ns: i64,
44 command: String,
46 warn_pending: bool,
48 warn_deferred: bool,
50}
51
52pub type EnvVars = HashMap<String, HashMap<EnvName, Value>>;
54
55#[derive(Debug, Clone)]
73pub struct Stack {
74 pub vars: Vec<(VarId, Value)>,
76 pub env_vars: Vec<Arc<EnvVars>>,
78 pub env_hidden: Arc<HashMap<String, HashSet<EnvName>>>,
80 pub env_hide_history: Arc<HashMap<String, HashSet<EnvName>>>,
85 pub active_overlays: Vec<String>,
87 pub arguments: ArgumentStack,
89 pub error_handlers: ErrorHandlerStack,
91 pub recursion_count: u64,
92 pub parent_stack: Option<Arc<Stack>>,
93 pub parent_deletions: Vec<VarId>,
95 pub deletions: Vec<VarId>,
97 pub config: Option<Arc<Config>>,
99 pub(crate) out_dest: StackOutDest,
100 pub suppress_stdin: bool,
104 pub active_scope_bindings: Vec<Arc<ScopeBindings>>,
109 pub ir_scope_regions: Vec<ScopeRegion>,
111 pub ir_instruction_index: Option<usize>,
113 last_result: Arc<Mutex<LastResultSlot>>,
118}
119
120impl Default for Stack {
121 fn default() -> Self {
122 Self::new()
123 }
124}
125
126impl Stack {
127 pub fn new() -> Self {
135 Self {
136 vars: Vec::new(),
137 env_vars: Vec::new(),
138 env_hidden: Arc::new(HashMap::new()),
139 env_hide_history: Arc::new(HashMap::new()),
140 active_overlays: vec![DEFAULT_OVERLAY_NAME.to_string()],
141 arguments: ArgumentStack::new(),
142 error_handlers: ErrorHandlerStack::new(),
143 recursion_count: 0,
144 parent_stack: None,
145 parent_deletions: vec![],
146 deletions: vec![],
147 config: None,
148 out_dest: StackOutDest::new(),
149 suppress_stdin: false,
150 active_scope_bindings: vec![],
151 ir_scope_regions: vec![],
152 ir_instruction_index: None,
153 last_result: Arc::new(Mutex::new(LastResultSlot::default())),
154 }
155 }
156
157 pub fn with_parent(parent: Arc<Stack>) -> Stack {
162 Stack {
163 env_vars: parent.env_vars.clone(),
165 env_hidden: parent.env_hidden.clone(),
166 env_hide_history: parent.env_hide_history.clone(),
167 active_overlays: parent.active_overlays.clone(),
168 arguments: ArgumentStack::new(),
169 error_handlers: ErrorHandlerStack::new(),
170 recursion_count: parent.recursion_count,
171 vars: vec![],
172 parent_deletions: vec![],
173 deletions: vec![],
174 config: parent.config.clone(),
175 out_dest: parent.out_dest.clone(),
176 suppress_stdin: parent.suppress_stdin,
177 active_scope_bindings: parent.active_scope_bindings.clone(),
179 ir_scope_regions: vec![],
181 ir_instruction_index: None,
182 last_result: parent.last_result.clone(),
184 parent_stack: Some(parent),
185 }
186 }
187
188 pub fn push_scope_bindings(&mut self, bindings: Arc<ScopeBindings>) {
190 self.active_scope_bindings.push(bindings);
191 }
192
193 pub fn pop_scope_bindings(&mut self) {
195 let popped = self.active_scope_bindings.pop();
196 debug_assert!(
197 popped.is_some(),
198 "pop_scope_bindings with empty active_scope_bindings (unbalanced push/pop)"
199 );
200 }
201
202 pub fn with_changes_from_child(parent: Arc<Stack>, child: Stack) -> Stack {
209 drop(child.parent_stack);
212 let mut unique_stack = Arc::unwrap_or_clone(parent);
213
214 unique_stack
215 .vars
216 .retain(|(var, _)| !child.parent_deletions.contains(var));
217 for (var, value) in child.vars {
218 unique_stack.add_var(var, value);
219 }
220 unique_stack.env_vars = child.env_vars;
221 unique_stack.env_hidden = child.env_hidden;
222 unique_stack.env_hide_history = child.env_hide_history;
223 unique_stack.active_overlays = child.active_overlays;
224 unique_stack.config = child.config;
225 unique_stack
227 }
228
229 pub fn with_env(
230 &mut self,
231 env_vars: &[Arc<EnvVars>],
232 env_hidden: &Arc<HashMap<String, HashSet<EnvName>>>,
233 ) {
234 if self.env_vars.iter().any(|scope| !scope.is_empty()) {
236 env_vars.clone_into(&mut self.env_vars);
237 }
238
239 if !self.env_hidden.is_empty() {
240 self.env_hidden.clone_from(env_hidden);
241 }
242 }
243
244 fn lookup_var(&self, var_id: VarId) -> Option<Value> {
246 if var_id == LAST_VARIABLE_ID {
247 return self.assemble_ans_record(Span::unknown());
248 }
249
250 for (id, val) in &self.vars {
251 if var_id == *id {
252 return Some(val.clone());
253 }
254 }
255
256 if let Some(stack) = &self.parent_stack
257 && !self.parent_deletions.contains(&var_id)
258 {
259 return stack.lookup_var(var_id);
260 }
261 None
262 }
263
264 fn with_last_result_slot<R>(&self, f: impl FnOnce(&LastResultSlot) -> R) -> R {
265 match self.last_result.lock() {
266 Ok(slot) => f(&slot),
267 Err(poisoned) => f(&poisoned.into_inner()),
269 }
270 }
271
272 fn with_last_result_slot_mut<R>(&self, f: impl FnOnce(&mut LastResultSlot) -> R) -> R {
273 match self.last_result.lock() {
274 Ok(mut slot) => f(&mut slot),
275 Err(poisoned) => {
276 let mut slot = poisoned.into_inner();
277 *slot = LastResultSlot::default();
279 f(&mut slot)
280 }
281 }
282 }
283
284 fn assemble_ans_record(&self, span: Span) -> Option<Value> {
290 self.with_last_result_slot(|slot| {
291 if !slot.present {
292 return None;
293 }
294 Some(match &slot.last {
295 Some(last) => Value::record(
296 record! {
297 "last" => last.clone().with_span(span),
298 "exit_code" => Value::int(slot.exit_code, span),
299 "duration" => Value::duration(slot.duration_ns, span),
300 "command" => Value::string(slot.command.clone(), span),
301 },
302 span,
303 ),
304 None => Value::record(
305 record! {
306 "exit_code" => Value::int(slot.exit_code, span),
307 "duration" => Value::duration(slot.duration_ns, span),
308 "command" => Value::string(slot.command.clone(), span),
309 },
310 span,
311 ),
312 })
313 })
314 }
315
316 pub fn clear_last_result(&mut self) {
318 self.with_last_result_slot_mut(|slot| {
319 if let Some(old) = slot.last.take() {
320 drop(old);
321 }
322 *slot = LastResultSlot::default();
323 });
324 }
325
326 pub fn clear_last_result_payload(&mut self) {
331 self.with_last_result_slot_mut(|slot| {
332 if let Some(old) = slot.last.take() {
333 drop(old);
334 }
335 slot.metadata = None;
336 slot.truncated = false;
337 slot.warn_pending = false;
338 slot.warn_deferred = false;
339 });
340 }
341
342 pub fn set_last_result(
348 &mut self,
349 value: Value,
350 metadata: Option<PipelineMetadata>,
351 budget: usize,
352 ) {
353 if budget == 0 {
354 self.clear_last_result_payload();
355 return;
356 }
357
358 let (stored, truncated) = truncate_value_to_budget(value, budget);
359 self.store_last_result_raw(stored, metadata, truncated);
360 }
361
362 pub fn store_last_result_raw(
367 &mut self,
368 value: Value,
369 metadata: Option<PipelineMetadata>,
370 truncated: bool,
371 ) {
372 self.with_last_result_slot_mut(|slot| {
373 if let Some(old) = slot.last.replace(value) {
374 drop(old);
375 }
376 slot.metadata = metadata;
377 slot.truncated = truncated;
378 slot.warn_pending = truncated;
379 slot.warn_deferred = false;
381 slot.present = true;
382 });
383 }
384
385 pub fn snapshot_ans_repl_metadata(
395 &mut self,
396 engine_state: &EngineState,
397 duration: std::time::Duration,
398 command: impl Into<String>,
399 ) {
400 let budget = self.get_config(engine_state).max_last_result_size_bytes();
401 let exit_code = self
402 .get_env_var(engine_state, "LAST_EXIT_CODE")
403 .and_then(|v| v.as_int().ok())
404 .unwrap_or(0);
405 let duration_ns = i64::try_from(duration.as_nanos()).unwrap_or(i64::MAX);
406 let command = command.into();
407
408 if budget == 0 {
409 self.clear_last_result_payload();
411 }
412
413 self.with_last_result_slot_mut(|slot| {
414 slot.exit_code = exit_code;
415 slot.duration_ns = duration_ns;
416 slot.command = command;
417 slot.present = true;
418 });
419 }
420
421 pub fn last_result_metadata(&self) -> Option<PipelineMetadata> {
423 self.with_last_result_slot(|slot| slot.metadata.clone())
424 }
425
426 pub fn last_result_memory_size(&self) -> usize {
428 self.with_last_result_slot(|slot| slot.last.as_ref().map(|v| v.memory_size()).unwrap_or(0))
429 }
430
431 pub const ANS_LAST_RESULT_METADATA_KEY: &str = "ans_last_result";
436
437 pub fn last_result_pipeline_data(&self, span: Span) -> PipelineData {
440 let mut metadata = self.last_result_metadata().unwrap_or_default();
441 metadata
443 .custom
444 .insert(Self::ANS_LAST_RESULT_METADATA_KEY, Value::bool(true, span));
445 let value = self
446 .assemble_ans_record(span)
447 .unwrap_or_else(|| Value::nothing(span));
448 PipelineData::value(value, Some(metadata))
449 }
450
451 pub fn last_result_was_truncated(&self) -> bool {
453 self.with_last_result_slot(|slot| slot.truncated)
454 }
455
456 pub fn defer_last_result_truncation_warning(&self) {
461 self.with_last_result_slot_mut(|slot| {
462 if slot.warn_pending {
463 slot.warn_pending = false;
464 slot.warn_deferred = true;
465 }
466 });
467 }
468
469 pub fn last_result_warn_pending(&self) -> bool {
471 self.with_last_result_slot(|slot| slot.warn_pending)
472 }
473
474 pub fn take_last_result_warn_deferred(&self) -> bool {
479 self.with_last_result_slot_mut(|slot| std::mem::take(&mut slot.warn_deferred))
480 }
481
482 pub fn flush_last_result_truncation_warning(&self, engine_state: &EngineState, span: Span) {
486 if !self.take_last_result_warn_deferred() {
487 return;
488 }
489 let limit_bytes = self.get_config(engine_state).max_last_result_size_bytes();
490 report_shell_warning(
491 Some(self),
492 engine_state,
493 &crate::ShellWarning::LastResultTruncated {
494 span,
495 limit_bytes,
496 help: Some(format!(
497 "Increase $env.config.max_last_result_size or use a smaller command result. Variable name is `${}`.",
498 crate::LAST_RESULT_VAR_NAME
499 )),
500 report_mode: crate::ReportMode::EveryUse,
502 },
503 );
504 }
505
506 pub fn get_var(&self, var_id: VarId, span: Span) -> Result<Value, ShellError> {
510 match self.lookup_var(var_id) {
511 Some(v) => Ok(v.with_span(span)),
512 None if var_id == LAST_VARIABLE_ID => Ok(Value::nothing(span)),
514 None => Err(ShellError::VariableNotFoundAtRuntime { span }),
515 }
516 }
517
518 pub fn get_var_with_origin(&self, var_id: VarId, span: Span) -> Result<Value, ShellError> {
523 match self.lookup_var(var_id) {
524 Some(v) => Ok(v),
525 None => {
526 if var_id == NU_VARIABLE_ID || var_id == ENV_VARIABLE_ID {
527 return Err(ShellError::Generic(GenericError::new(
528 "Built-in variables `$env` and `$nu` have no metadata",
529 "no metadata available",
530 span,
531 )));
532 }
533 Err(ShellError::VariableNotFoundAtRuntime { span })
534 }
535 }
536 }
537
538 pub fn get_config(&self, engine_state: &EngineState) -> Arc<Config> {
542 self.config
543 .clone()
544 .unwrap_or_else(|| engine_state.config.clone())
545 }
546
547 pub fn update_config(&mut self, engine_state: &EngineState) -> Result<(), ShellError> {
552 if let Some(value) = self.get_env_var(engine_state, "config") {
553 let old = self.get_config(engine_state);
554 let mut config = (*old).clone();
555 let result = config.update_from_value_with_options(
556 &old,
557 value,
558 engine_state.history_locked_after_startup,
559 );
560 self.add_env_var("config".into(), config.clone().into_value(value.span()));
562 self.config = Some(config.into());
563 if let Some(warning) = result? {
564 report_shell_warning(Some(self), engine_state, &warning);
565 }
566 } else {
567 self.config = None;
568 }
569 Ok(())
570 }
571
572 pub fn add_var(&mut self, var_id: VarId, value: Value) {
573 for (id, val) in &mut self.vars {
575 if *id == var_id {
576 *val = value;
577 return;
578 }
579 }
580 self.vars.push((var_id, value));
581 }
582
583 pub fn get_var_mut(&mut self, var_id: VarId) -> Option<&mut Value> {
590 if let Some(pos) = self.vars.iter().position(|(id, _)| var_id == *id) {
592 return Some(&mut self.vars[pos].1);
593 }
594 if let Some(parent) = &self.parent_stack
596 && !self.parent_deletions.contains(&var_id)
597 {
598 let value = parent.lookup_var(var_id)?;
599 self.vars.push((var_id, value));
600 return self.vars.last_mut().map(|(_, val)| val);
601 }
602 None
603 }
604
605 pub fn upsert_var_cell_path(
610 &mut self,
611 var_id: VarId,
612 members: &[PathMember],
613 new_value: Value,
614 span: Span,
615 ) -> Result<(), ShellError> {
616 let value = self
617 .get_var_mut(var_id)
618 .ok_or(ShellError::VariableNotFoundAtRuntime { span })?;
619 value.upsert_data_at_cell_path(members, new_value)
620 }
621
622 pub fn remove_var(&mut self, var_id: VarId) {
623 for (idx, (id, _)) in self.vars.iter().enumerate() {
624 if *id == var_id {
625 self.vars.remove(idx);
626 break;
627 }
628 }
629 if self.parent_stack.is_some() {
632 self.parent_deletions.push(var_id);
633 }
634 self.deletions.push(var_id);
635 }
636
637 pub fn add_env_var(&mut self, var: String, value: Value) {
638 if let Some(last_overlay) = self.active_overlays.last().cloned() {
639 let env_name = EnvName::from(var);
640 self.clear_env_var_marks_in_active_overlay(&last_overlay, &env_name);
641
642 if let Some(scope) = self.env_vars.last_mut() {
643 let scope = Arc::make_mut(scope);
644 if let Some(env_vars) = scope.get_mut(&last_overlay) {
645 env_vars.insert(env_name, value);
646 } else {
647 scope.insert(last_overlay, [(env_name, value)].into_iter().collect());
648 }
649 } else {
650 self.env_vars.push(Arc::new(
651 [(last_overlay, [(env_name, value)].into_iter().collect())]
652 .into_iter()
653 .collect(),
654 ));
655 }
656 } else {
657 panic!("internal error: no active overlay");
659 }
660 }
661
662 fn clear_env_var_marks_in_active_overlay(&mut self, overlay: &str, env_name: &EnvName) {
663 if let Some(env_hidden) = Arc::make_mut(&mut self.env_hidden).get_mut(overlay) {
664 env_hidden.remove(env_name);
666 }
667
668 if let Some(hide_history) = Arc::make_mut(&mut self.env_hide_history).get_mut(overlay) {
669 hide_history.remove(env_name);
670 }
671 }
672
673 pub fn set_last_exit_code(&mut self, code: i32, span: Span) {
674 self.add_env_var("LAST_EXIT_CODE".into(), Value::int(code.into(), span));
675 }
676
677 pub fn set_last_error(&mut self, error: &ShellError) {
678 if let Some(code) = error.external_exit_code() {
679 self.set_last_exit_code(code.item, code.span);
680 } else if let Some(code) = error.exit_code() {
681 self.set_last_exit_code(code, Span::unknown());
682 }
683 }
684
685 pub fn last_overlay_name(&self) -> Result<String, ShellError> {
686 self.active_overlays
687 .last()
688 .cloned()
689 .ok_or_else(|| ShellError::NushellFailed {
690 msg: "No active overlay".into(),
691 })
692 }
693
694 pub fn captures_to_stack(&self, captures: Vec<(VarId, Value)>) -> Stack {
696 self.captures_to_stack_preserve_out_dest(captures)
697 .collect_value()
698 }
699
700 pub fn captures_to_stack_preserve_out_dest(&self, captures: Vec<(VarId, Value)>) -> Stack {
706 let mut env_vars = self.env_vars.clone();
707 env_vars.push(Arc::new(HashMap::new()));
708
709 Stack {
710 vars: captures,
711 env_vars,
712 env_hidden: self.env_hidden.clone(),
713 env_hide_history: self.env_hide_history.clone(),
714 active_overlays: self.active_overlays.clone(),
715 arguments: ArgumentStack::new(),
716 error_handlers: ErrorHandlerStack::new(),
717 recursion_count: self.recursion_count,
718 parent_stack: Some(Arc::new(self.clone())),
721 parent_deletions: vec![],
722 deletions: vec![],
723 config: self.config.clone(),
724 out_dest: self.out_dest.clone(),
725 suppress_stdin: self.suppress_stdin,
726 active_scope_bindings: self.active_scope_bindings.clone(),
728 ir_scope_regions: vec![],
729 ir_instruction_index: None,
730 last_result: self.last_result.clone(),
732 }
733 }
734
735 pub fn gather_captures(&self, engine_state: &EngineState, captures: &[(VarId, Span)]) -> Stack {
736 let mut vars = Vec::with_capacity(captures.len());
737
738 let fake_span = Span::new(0, 0);
739
740 for (capture, _) in captures {
741 if let Ok(value) = self.get_var(*capture, fake_span) {
744 vars.push((*capture, value));
745 } else if let Some(const_val) = &engine_state.get_var(*capture).const_val {
746 vars.push((*capture, const_val.clone()));
747 }
748 }
749
750 let mut env_vars = self.env_vars.clone();
751 env_vars.push(Arc::new(HashMap::new()));
752
753 Stack {
754 vars,
755 env_vars,
756 env_hidden: self.env_hidden.clone(),
757 env_hide_history: self.env_hide_history.clone(),
758 active_overlays: self.active_overlays.clone(),
759 arguments: ArgumentStack::new(),
760 error_handlers: ErrorHandlerStack::new(),
761 recursion_count: self.recursion_count,
762 parent_stack: Some(Arc::new(self.clone())),
763 parent_deletions: vec![],
764 deletions: vec![],
765 config: self.config.clone(),
766 out_dest: self.out_dest.clone(),
767 suppress_stdin: self.suppress_stdin,
768 active_scope_bindings: self.active_scope_bindings.clone(),
770 ir_scope_regions: vec![],
771 ir_instruction_index: None,
772 last_result: self.last_result.clone(),
774 }
775 }
776
777 pub fn get_env_vars(&self, engine_state: &EngineState) -> HashMap<String, Value> {
779 let mut result = HashMap::new();
780
781 for active_overlay in self.active_overlays.iter() {
782 if let Some(env_vars) = engine_state.env_vars.get(active_overlay) {
783 result.extend(
784 env_vars
785 .iter()
786 .filter(|(k, _)| {
787 if let Some(env_hidden) = self.env_hidden.get(active_overlay) {
788 !env_hidden.contains(*k)
789 } else {
790 true
792 }
793 })
794 .map(|(k, v)| (k.as_str().to_string(), v.clone()))
795 .collect::<HashMap<String, Value>>(),
796 );
797 }
798 }
799
800 result.extend(self.get_stack_env_vars());
801
802 result
803 }
804
805 pub fn get_stack_env_vars(&self) -> HashMap<String, Value> {
807 let mut result = HashMap::new();
808
809 for scope in &self.env_vars {
810 for active_overlay in self.active_overlays.iter() {
811 if let Some(env_vars) = scope.get(active_overlay) {
812 result.extend(
813 env_vars
814 .iter()
815 .map(|(k, v)| (k.as_str().to_string(), v.clone())),
816 );
817 }
818 }
819 }
820
821 result
822 }
823
824 pub fn get_stack_overlay_env_vars(&self, overlay_name: &str) -> HashMap<String, Value> {
826 let mut result = HashMap::new();
827
828 for scope in &self.env_vars {
829 if let Some(active_overlay) = self.active_overlays.iter().find(|n| n == &overlay_name)
830 && let Some(env_vars) = scope.get(active_overlay)
831 {
832 result.extend(
833 env_vars
834 .iter()
835 .map(|(k, v)| (k.as_str().to_string(), v.clone())),
836 );
837 }
838 }
839
840 result
841 }
842
843 pub fn get_hidden_env_vars(
845 &self,
846 excluded_overlay_name: &str,
847 engine_state: &EngineState,
848 ) -> HashMap<String, Value> {
849 let mut result = HashMap::new();
850
851 for overlay_name in self.active_overlays.iter().rev() {
852 if overlay_name == excluded_overlay_name {
853 continue;
854 }
855 if let Some(env_names) = self.env_hidden.get(overlay_name) {
856 for n in env_names {
857 if result.contains_key(n.as_str()) {
858 continue;
859 }
860 if let Some(Some(v)) = engine_state
862 .env_vars
863 .get(overlay_name)
864 .map(|env_vars| env_vars.get(n))
865 {
866 result.insert(n.as_str().to_string(), v.clone());
867 }
868 }
869 }
870 }
871 result
872 }
873
874 pub fn get_env_var_names(&self, engine_state: &EngineState) -> HashSet<String> {
876 let mut result = HashSet::new();
877
878 for active_overlay in self.active_overlays.iter() {
879 if let Some(env_vars) = engine_state.env_vars.get(active_overlay) {
880 result.extend(
881 env_vars
882 .keys()
883 .filter(|k| {
884 if let Some(env_hidden) = self.env_hidden.get(active_overlay) {
885 !env_hidden.contains(*k)
886 } else {
887 true
889 }
890 })
891 .map(|k| k.as_str().to_string())
892 .collect::<HashSet<String>>(),
893 );
894 }
895 }
896
897 for scope in &self.env_vars {
898 for active_overlay in self.active_overlays.iter() {
899 if let Some(env_vars) = scope.get(active_overlay) {
900 result.extend(
901 env_vars
902 .keys()
903 .map(|k| k.as_str().to_string())
904 .collect::<HashSet<String>>(),
905 );
906 }
907 }
908 }
909
910 result
911 }
912
913 pub fn get_env_var<'a>(
914 &'a self,
915 engine_state: &'a EngineState,
916 name: &str,
917 ) -> Option<&'a Value> {
918 let env_name = EnvName::from(name);
919
920 for scope in self.env_vars.iter().rev() {
921 for active_overlay in self.active_overlays.iter().rev() {
922 if let Some(env_vars) = scope.get(active_overlay)
923 && let Some(v) = env_vars.get(&env_name)
924 {
925 return Some(v);
926 }
927 }
928 }
929
930 for active_overlay in self.active_overlays.iter().rev() {
931 if !self.is_env_hidden_in_overlay(active_overlay, &env_name)
932 && let Some(env_vars) = engine_state.env_vars.get(active_overlay)
933 && let Some(v) = env_vars.get(&env_name)
934 {
935 return Some(v);
936 }
937 }
938 None
939 }
940
941 pub fn has_env_var(&self, engine_state: &EngineState, name: &str) -> bool {
942 let env_name = EnvName::from(name);
943
944 for scope in self.env_vars.iter().rev() {
945 for active_overlay in self.active_overlays.iter().rev() {
946 if let Some(env_vars) = scope.get(active_overlay)
947 && env_vars.contains_key(&env_name)
948 {
949 return true;
950 }
951 }
952 }
953
954 for active_overlay in self.active_overlays.iter().rev() {
955 if !self.is_env_hidden_in_overlay(active_overlay, &env_name)
956 && let Some(env_vars) = engine_state.env_vars.get(active_overlay)
957 && env_vars.contains_key(&env_name)
958 {
959 return true;
960 }
961 }
962
963 false
964 }
965
966 pub fn remove_env_var(&mut self, engine_state: &EngineState, name: &str) -> bool {
974 let env_name = EnvName::from(name);
975
976 self.remove_env_var_from_stack(&env_name)
977 || self.hide_engine_state_env_var(engine_state, &env_name)
978 }
979
980 fn remove_env_var_from_stack(&mut self, env_name: &EnvName) -> bool {
983 self.env_vars
984 .iter_mut()
985 .rev()
986 .map(Arc::make_mut)
987 .find_map(|scope| {
988 self.active_overlays
989 .iter()
990 .rev()
991 .find_map(|active_overlay| scope.get_mut(active_overlay)?.remove(env_name))
992 })
993 .is_some()
994 }
995
996 fn hide_engine_state_env_var(
1001 &mut self,
1002 engine_state: &EngineState,
1003 env_name: &EnvName,
1004 ) -> bool {
1005 let overlay_containing_env_var = self.active_overlays.iter().rev().find(|active_overlay| {
1006 engine_state
1007 .env_vars
1008 .get(active_overlay.as_str())
1009 .is_some_and(|env_vars| env_vars.contains_key(env_name))
1010 });
1011
1012 let Some(overlay_containing_env_var) = overlay_containing_env_var else {
1013 return false;
1014 };
1015
1016 let env_hidden = Arc::make_mut(&mut self.env_hidden);
1017
1018 if env_hidden
1019 .get(overlay_containing_env_var.as_str())
1020 .is_some_and(|hidden_vars| hidden_vars.contains(env_name))
1021 {
1022 return false;
1023 }
1024
1025 env_hidden
1026 .entry(overlay_containing_env_var.clone())
1027 .or_default()
1028 .insert(env_name.clone());
1029 true
1030 }
1031
1032 fn record_env_var_hide_in_active_overlay(&mut self, env_name: &EnvName) -> bool {
1035 let Some(active_overlay) = self.active_overlays.last().cloned() else {
1036 return false;
1037 };
1038
1039 Arc::make_mut(&mut self.env_hide_history)
1040 .entry(active_overlay)
1041 .or_default()
1042 .insert(env_name.clone())
1043 }
1044
1045 fn is_env_var_hide_recorded(&self, env_name: &EnvName) -> bool {
1046 self.active_overlays
1047 .iter()
1048 .rev()
1049 .filter_map(|overlay| self.env_hide_history.get(overlay))
1050 .any(|hidden_vars| hidden_vars.contains(env_name))
1051 }
1052
1053 fn is_env_hidden_in_overlay(&self, overlay: &str, env_name: &EnvName) -> bool {
1054 self.env_hidden
1055 .get(overlay)
1056 .is_some_and(|hidden_vars| hidden_vars.contains(env_name))
1057 }
1058
1059 pub fn is_env_var_hidden(&self, name: &str) -> bool {
1065 let env_name = EnvName::from(name);
1066
1067 self.active_overlays
1068 .iter()
1069 .rev()
1070 .any(|overlay| self.is_env_hidden_in_overlay(overlay, &env_name))
1071 || self.is_env_var_hide_recorded(&env_name)
1072 }
1073
1074 pub fn hide_env_var(&mut self, engine_state: &EngineState, name: &str) -> bool {
1082 let env_name = EnvName::from(name);
1083
1084 if self.is_env_var_hide_recorded(&env_name) {
1086 return false;
1087 }
1088
1089 if self.remove_env_var_from_stack(&env_name) {
1090 self.record_env_var_hide_in_active_overlay(&env_name);
1091
1092 if !self.has_env_var_in_stack(&env_name) {
1093 self.hide_engine_state_env_var(engine_state, &env_name);
1094 }
1095 return true;
1096 }
1097
1098 if self.hide_engine_state_env_var(engine_state, &env_name) {
1099 self.record_env_var_hide_in_active_overlay(&env_name);
1100 return true;
1101 }
1102
1103 false
1104 }
1105
1106 fn has_env_var_in_stack(&self, name: &EnvName) -> bool {
1108 self.env_vars.iter().rev().any(|scope| {
1109 self.active_overlays
1110 .iter()
1111 .rev()
1112 .filter_map(|active_overlay| scope.get(active_overlay))
1113 .any(|env_vars| env_vars.contains_key(name))
1114 })
1115 }
1116
1117 pub fn has_env_overlay(&self, name: &str, engine_state: &EngineState) -> bool {
1118 for scope in self.env_vars.iter().rev() {
1119 if scope.contains_key(name) {
1120 return true;
1121 }
1122 }
1123
1124 engine_state.env_vars.contains_key(name)
1125 }
1126
1127 pub fn is_overlay_active(&self, name: &str) -> bool {
1128 self.active_overlays.iter().any(|n| n == name)
1129 }
1130
1131 pub fn add_overlay(&mut self, name: String) {
1132 self.active_overlays.retain(|o| o != &name);
1133 self.active_overlays.push(name);
1134 }
1135
1136 pub fn remove_overlay(&mut self, name: &str) {
1137 self.active_overlays.retain(|o| o != name);
1138 }
1139
1140 pub fn stdout(&self) -> &OutDest {
1146 self.out_dest.stdout()
1147 }
1148
1149 pub fn stderr(&self) -> &OutDest {
1155 self.out_dest.stderr()
1156 }
1157
1158 pub fn pipe_stdout(&self) -> Option<&OutDest> {
1160 self.out_dest.pipe_stdout.as_ref()
1161 }
1162
1163 pub fn pipe_stderr(&self) -> Option<&OutDest> {
1165 self.out_dest.pipe_stderr.as_ref()
1166 }
1167
1168 pub fn invocation_stdout(&self) -> Option<&OutDest> {
1176 self.out_dest.invocation_stdout.last()
1177 }
1178
1179 #[must_use]
1188 pub fn is_stdout_redirected(&self) -> bool {
1189 self.invocation_stdout()
1190 .unwrap_or_else(|| self.stdout())
1191 .is_redirected()
1192 }
1193
1194 pub fn with_invocation_stdout(self, dest: OutDest) -> StackWithInvocation {
1206 StackWithInvocation::new(self, dest)
1207 }
1208
1209 pub fn start_collect_value(&mut self) -> StackCollectValueGuard<'_> {
1213 StackCollectValueGuard::new(self)
1214 }
1215
1216 pub fn use_call_arg_out_dest(&mut self) -> StackCallArgGuard<'_> {
1220 StackCallArgGuard::new(self)
1221 }
1222
1223 pub fn push_redirection(
1225 &mut self,
1226 stdout: Option<Redirection>,
1227 stderr: Option<Redirection>,
1228 ) -> StackIoGuard<'_> {
1229 StackIoGuard::new(self, stdout, stderr)
1230 }
1231
1232 pub fn collect_value(mut self) -> Self {
1239 self.out_dest.pipe_stdout = Some(OutDest::Value);
1240 self.out_dest.pipe_stderr = None;
1241 self
1242 }
1243
1244 pub fn capture_all(mut self) -> Self {
1253 self.out_dest.pipe_stdout = Some(OutDest::Value);
1254 self.out_dest.pipe_stderr = Some(OutDest::Value);
1255 self
1256 }
1257
1258 pub fn reset_out_dest(mut self) -> Self {
1263 self.out_dest = StackOutDest::new();
1264 self
1265 }
1266
1267 pub fn suppress_output(mut self) -> Self {
1272 self.out_dest.stdout = OutDest::Null;
1273 self.out_dest.stderr = OutDest::Null;
1274 self
1275 }
1276
1277 pub fn suppress_stdin(mut self) -> Self {
1288 self.suppress_stdin = true;
1289 self
1290 }
1291
1292 pub fn require_stdin(&self, span: Span) -> Result<(), ShellError> {
1298 if self.suppress_stdin {
1299 Err(ShellError::Generic(
1300 GenericError::new(
1301 "Interactive input is unavailable in this context",
1302 "this command reads from the terminal",
1303 span,
1304 )
1305 .with_help(
1306 "this stack is detached from the terminal (completion worker or MCP), so \
1307 interactive commands like `input`, `input list`, `input listen`, and \
1308 `term query` cannot run here",
1309 ),
1310 ))
1311 } else {
1312 Ok(())
1313 }
1314 }
1315
1316 pub fn reset_pipes(mut self) -> Self {
1321 self.out_dest.pipe_stdout = None;
1322 self.out_dest.pipe_stderr = None;
1323 self
1324 }
1325
1326 pub fn stdout_file(mut self, file: File) -> Self {
1365 self.out_dest.stdout = OutDest::File(Arc::new(file));
1366 self
1367 }
1368
1369 pub fn stderr_file(mut self, file: File) -> Self {
1373 self.out_dest.stderr = OutDest::File(Arc::new(file));
1374 self
1375 }
1376
1377 pub fn set_cwd(&mut self, path: impl AsRef<std::path::Path>) -> Result<(), ShellError> {
1382 fn error(msg: &str) -> Result<(), ShellError> {
1385 Err(ShellError::Generic(GenericError::new_internal(
1386 msg.to_string(),
1387 "",
1388 )))
1389 }
1390
1391 let path = path.as_ref();
1392
1393 if !path.is_absolute() {
1394 if let Some(Component::Prefix(_)) = path.components().next() {
1395 return Err(ShellError::Generic(
1396 GenericError::new_internal("Cannot set $env.PWD to a prefix-only path", "")
1397 .with_help(format!(
1398 "Try to use {}{MAIN_SEPARATOR} instead",
1399 path.display()
1400 )),
1401 ));
1402 }
1403
1404 error("Cannot set $env.PWD to a non-absolute path")
1405 } else if !path.exists() {
1406 error("Cannot set $env.PWD to a non-existent directory")
1407 } else if !path.is_dir() {
1408 error("Cannot set $env.PWD to a non-directory")
1409 } else {
1410 let path = nu_path::strip_trailing_slash(path);
1412 let value = Value::string(path.to_string_lossy(), Span::unknown());
1413 self.add_env_var("PWD".into(), value);
1414 Ok(())
1415 }
1416 }
1417}
1418
1419#[cfg(test)]
1420mod test {
1421 use std::sync::Arc;
1422
1423 use crate::{Span, Value, VarId, engine::EngineState};
1424
1425 use super::Stack;
1426
1427 #[test]
1428 fn test_children_see_inner_values() {
1429 let mut original = Stack::new();
1430 original.add_var(VarId::new(0), Value::test_string("hello"));
1431
1432 let cloned = Stack::with_parent(Arc::new(original));
1433 assert_eq!(
1434 cloned.get_var(VarId::new(0), Span::test_data()),
1435 Ok(Value::test_string("hello"))
1436 );
1437 }
1438
1439 #[test]
1440 fn test_children_dont_see_deleted_values() {
1441 let mut original = Stack::new();
1442 original.add_var(VarId::new(0), Value::test_string("hello"));
1443
1444 let mut cloned = Stack::with_parent(Arc::new(original));
1445 cloned.remove_var(VarId::new(0));
1446
1447 assert_eq!(
1448 cloned.get_var(VarId::new(0), Span::test_data()),
1449 Err(crate::ShellError::VariableNotFoundAtRuntime {
1450 span: Span::test_data()
1451 })
1452 );
1453 }
1454
1455 #[test]
1456 fn test_children_changes_override_parent() {
1457 let mut original = Stack::new();
1458 original.add_var(VarId::new(0), Value::test_string("hello"));
1459
1460 let mut cloned = Stack::with_parent(Arc::new(original));
1461 cloned.add_var(VarId::new(0), Value::test_string("there"));
1462 assert_eq!(
1463 cloned.get_var(VarId::new(0), Span::test_data()),
1464 Ok(Value::test_string("there"))
1465 );
1466
1467 cloned.remove_var(VarId::new(0));
1468 assert_eq!(
1470 cloned.get_var(VarId::new(0), Span::test_data()),
1471 Err(crate::ShellError::VariableNotFoundAtRuntime {
1472 span: Span::test_data()
1473 })
1474 );
1475 }
1476 #[test]
1477 fn test_children_changes_persist_in_offspring() {
1478 let mut original = Stack::new();
1479 original.add_var(VarId::new(0), Value::test_string("hello"));
1480
1481 let mut cloned = Stack::with_parent(Arc::new(original));
1482 cloned.add_var(VarId::new(1), Value::test_string("there"));
1483
1484 cloned.remove_var(VarId::new(0));
1485 let cloned = Stack::with_parent(Arc::new(cloned));
1486
1487 assert_eq!(
1488 cloned.get_var(VarId::new(0), Span::test_data()),
1489 Err(crate::ShellError::VariableNotFoundAtRuntime {
1490 span: Span::test_data()
1491 })
1492 );
1493
1494 assert_eq!(
1495 cloned.get_var(VarId::new(1), Span::test_data()),
1496 Ok(Value::test_string("there"))
1497 );
1498 }
1499
1500 #[test]
1501 fn test_merging_children_back_to_parent() {
1502 let mut original = Stack::new();
1503 let engine_state = EngineState::new();
1504 original.add_var(VarId::new(0), Value::test_string("hello"));
1505
1506 let original_arc = Arc::new(original);
1507 let mut cloned = Stack::with_parent(original_arc.clone());
1508 cloned.add_var(VarId::new(1), Value::test_string("there"));
1509
1510 cloned.remove_var(VarId::new(0));
1511
1512 cloned.add_env_var(
1513 "ADDED_IN_CHILD".to_string(),
1514 Value::test_string("New Env Var"),
1515 );
1516
1517 let original = Stack::with_changes_from_child(original_arc, cloned);
1518
1519 assert_eq!(
1520 original.get_var(VarId::new(0), Span::test_data()),
1521 Err(crate::ShellError::VariableNotFoundAtRuntime {
1522 span: Span::test_data()
1523 })
1524 );
1525
1526 assert_eq!(
1527 original.get_var(VarId::new(1), Span::test_data()),
1528 Ok(Value::test_string("there"))
1529 );
1530
1531 assert_eq!(
1532 original
1533 .get_env_var(&engine_state, "ADDED_IN_CHILD")
1534 .cloned(),
1535 Some(Value::test_string("New Env Var")),
1536 );
1537 }
1538
1539 #[test]
1540 fn test_get_var_mut_local_in_place() {
1541 use crate::ast::PathMember;
1542 use crate::casing::Casing;
1543 use crate::record;
1544
1545 let mut stack = Stack::new();
1546 let var_id = VarId::new(0);
1547 stack.add_var(
1548 var_id,
1549 Value::test_record(record! { "a" => Value::test_int(1) }),
1550 );
1551
1552 let path = vec![PathMember::test_string("a", false, Casing::Sensitive)];
1553 stack
1554 .upsert_var_cell_path(var_id, &path, Value::test_int(2), Span::test_data())
1555 .expect("upsert should succeed");
1556
1557 assert_eq!(
1558 stack.get_var(var_id, Span::test_data()),
1559 Ok(Value::test_record(record! { "a" => Value::test_int(2) }))
1560 );
1561 assert_eq!(stack.vars.len(), 1);
1563 }
1564
1565 #[test]
1566 fn test_get_var_mut_pulls_from_parent() {
1567 use crate::ast::PathMember;
1568 use crate::casing::Casing;
1569 use crate::record;
1570
1571 let mut parent = Stack::new();
1572 let var_id = VarId::new(0);
1573 parent.add_var(
1574 var_id,
1575 Value::test_record(record! { "a" => Value::test_int(1) }),
1576 );
1577
1578 let mut child = Stack::with_parent(Arc::new(parent));
1579 assert!(child.vars.is_empty());
1580
1581 let path = vec![PathMember::test_string("a", false, Casing::Sensitive)];
1582 child
1583 .upsert_var_cell_path(var_id, &path, Value::test_int(9), Span::test_data())
1584 .expect("upsert should succeed");
1585
1586 assert_eq!(child.vars.len(), 1);
1588 assert_eq!(
1589 child.get_var(var_id, Span::test_data()),
1590 Ok(Value::test_record(record! { "a" => Value::test_int(9) }))
1591 );
1592
1593 child
1595 .upsert_var_cell_path(var_id, &path, Value::test_int(10), Span::test_data())
1596 .expect("second upsert should succeed");
1597 assert_eq!(child.vars.len(), 1);
1598 assert_eq!(
1599 child.get_var(var_id, Span::test_data()),
1600 Ok(Value::test_record(record! { "a" => Value::test_int(10) }))
1601 );
1602 }
1603
1604 #[test]
1605 fn test_upsert_var_cell_path_missing_and_deleted() {
1606 use crate::ast::PathMember;
1607 use crate::casing::Casing;
1608
1609 let mut stack = Stack::new();
1610 let var_id = VarId::new(0);
1611 let path = vec![PathMember::test_string("a", false, Casing::Sensitive)];
1612
1613 assert!(matches!(
1614 stack.upsert_var_cell_path(var_id, &path, Value::test_int(1), Span::test_data()),
1615 Err(crate::ShellError::VariableNotFoundAtRuntime { .. })
1616 ));
1617
1618 let mut parent = Stack::new();
1619 parent.add_var(var_id, Value::test_int(1));
1620 let mut child = Stack::with_parent(Arc::new(parent));
1621 child.remove_var(var_id);
1622
1623 assert!(matches!(
1624 child.upsert_var_cell_path(var_id, &path, Value::test_int(2), Span::test_data()),
1625 Err(crate::ShellError::VariableNotFoundAtRuntime { .. })
1626 ));
1627 assert!(child.get_var_mut(var_id).is_none());
1628 }
1629}