mirror of
https://github.com/nushell/nushell.git
synced 2025-04-07 18:55:30 +02:00
Allow for stacks to have parents (#11654)
This is another attempt on #11288 This allows for a `Stack` to have a parent stack (behind an `Arc`). This is being added to avoid constant stack copying in REPL code. Concretely the following changes are included here: - `Stack` can now have a `parent_stack`, pointing to another stack - variable lookups can fallback to this parent stack (env vars and everything else is still copied) - REPL code has been reworked so that we use parenting rather than cloning. A REPL-code-specific trait helps to ensure that we do not accidentally trigger a full clone of the main stack - A property test has been added to make sure that parenting "looks the same" as cloning for consumers of `Stack` objects --------- Co-authored-by: Raphael Gaschignard <rtpg@rokkenjima.local> Co-authored-by: Ian Manske <ian.manske@pm.me>
This commit is contained in:
parent
c90640411d
commit
d8f13b36b1
@ -1,7 +1,7 @@
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Type, Value};
|
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Type, Value};
|
||||||
use reedline::Highlighter;
|
use reedline::{Highlighter, StyledText};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct NuHighlight;
|
pub struct NuHighlight;
|
||||||
@ -64,3 +64,16 @@ impl Command for NuHighlight {
|
|||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A highlighter that does nothing
|
||||||
|
///
|
||||||
|
/// Used to remove highlighting from a reedline instance
|
||||||
|
/// (letting NuHighlighter structs be dropped)
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct NoOpHighlighter {}
|
||||||
|
|
||||||
|
impl Highlighter for NoOpHighlighter {
|
||||||
|
fn highlight(&self, _line: &str, _cursor: usize) -> reedline::StyledText {
|
||||||
|
StyledText::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -102,12 +102,10 @@ fn get_prompt_string(
|
|||||||
pub(crate) fn update_prompt(
|
pub(crate) fn update_prompt(
|
||||||
config: &Config,
|
config: &Config,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &Stack,
|
stack: &mut Stack,
|
||||||
nu_prompt: &mut NushellPrompt,
|
nu_prompt: &mut NushellPrompt,
|
||||||
) {
|
) {
|
||||||
let mut stack = stack.clone();
|
let left_prompt_string = get_prompt_string(PROMPT_COMMAND, config, engine_state, stack);
|
||||||
|
|
||||||
let left_prompt_string = get_prompt_string(PROMPT_COMMAND, config, engine_state, &mut stack);
|
|
||||||
|
|
||||||
// Now that we have the prompt string lets ansify it.
|
// Now that we have the prompt string lets ansify it.
|
||||||
// <133 A><prompt><133 B><command><133 C><command output>
|
// <133 A><prompt><133 B><command><133 C><command output>
|
||||||
@ -123,20 +121,18 @@ pub(crate) fn update_prompt(
|
|||||||
left_prompt_string
|
left_prompt_string
|
||||||
};
|
};
|
||||||
|
|
||||||
let right_prompt_string =
|
let right_prompt_string = get_prompt_string(PROMPT_COMMAND_RIGHT, config, engine_state, stack);
|
||||||
get_prompt_string(PROMPT_COMMAND_RIGHT, config, engine_state, &mut stack);
|
|
||||||
|
|
||||||
let prompt_indicator_string =
|
let prompt_indicator_string = get_prompt_string(PROMPT_INDICATOR, config, engine_state, stack);
|
||||||
get_prompt_string(PROMPT_INDICATOR, config, engine_state, &mut stack);
|
|
||||||
|
|
||||||
let prompt_multiline_string =
|
let prompt_multiline_string =
|
||||||
get_prompt_string(PROMPT_MULTILINE_INDICATOR, config, engine_state, &mut stack);
|
get_prompt_string(PROMPT_MULTILINE_INDICATOR, config, engine_state, stack);
|
||||||
|
|
||||||
let prompt_vi_insert_string =
|
let prompt_vi_insert_string =
|
||||||
get_prompt_string(PROMPT_INDICATOR_VI_INSERT, config, engine_state, &mut stack);
|
get_prompt_string(PROMPT_INDICATOR_VI_INSERT, config, engine_state, stack);
|
||||||
|
|
||||||
let prompt_vi_normal_string =
|
let prompt_vi_normal_string =
|
||||||
get_prompt_string(PROMPT_INDICATOR_VI_NORMAL, config, engine_state, &mut stack);
|
get_prompt_string(PROMPT_INDICATOR_VI_NORMAL, config, engine_state, stack);
|
||||||
|
|
||||||
// apply the other indicators
|
// apply the other indicators
|
||||||
nu_prompt.update_all_prompt_strings(
|
nu_prompt.update_all_prompt_strings(
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
completions::NuCompleter,
|
completions::NuCompleter,
|
||||||
|
nu_highlight::NoOpHighlighter,
|
||||||
prompt_update,
|
prompt_update,
|
||||||
reedline_config::{add_menus, create_keybindings, KeybindingsMode},
|
reedline_config::{add_menus, create_keybindings, KeybindingsMode},
|
||||||
util::eval_source,
|
util::eval_source,
|
||||||
@ -22,8 +23,8 @@ use nu_protocol::{
|
|||||||
};
|
};
|
||||||
use nu_utils::utils::perf;
|
use nu_utils::utils::perf;
|
||||||
use reedline::{
|
use reedline::{
|
||||||
CursorConfig, CwdAwareHinter, EditCommand, Emacs, FileBackedHistory, HistorySessionId,
|
CursorConfig, CwdAwareHinter, DefaultCompleter, EditCommand, Emacs, FileBackedHistory,
|
||||||
Reedline, SqliteBackedHistory, Vi,
|
HistorySessionId, Reedline, SqliteBackedHistory, Vi,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
@ -32,7 +33,7 @@ use std::{
|
|||||||
panic::{catch_unwind, AssertUnwindSafe},
|
panic::{catch_unwind, AssertUnwindSafe},
|
||||||
path::Path,
|
path::Path,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::atomic::Ordering,
|
sync::{atomic::Ordering, Arc},
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
use sysinfo::System;
|
use sysinfo::System;
|
||||||
@ -47,17 +48,21 @@ const PRE_EXECUTE_MARKER: &str = "\x1b]133;C\x1b\\";
|
|||||||
// const CMD_FINISHED_MARKER: &str = "\x1b]133;D;{}\x1b\\";
|
// const CMD_FINISHED_MARKER: &str = "\x1b]133;D;{}\x1b\\";
|
||||||
const RESET_APPLICATION_MODE: &str = "\x1b[?1l";
|
const RESET_APPLICATION_MODE: &str = "\x1b[?1l";
|
||||||
|
|
||||||
///
|
|
||||||
/// The main REPL loop, including spinning up the prompt itself.
|
/// The main REPL loop, including spinning up the prompt itself.
|
||||||
///
|
|
||||||
pub fn evaluate_repl(
|
pub fn evaluate_repl(
|
||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
stack: &mut Stack,
|
stack: Stack,
|
||||||
nushell_path: &str,
|
nushell_path: &str,
|
||||||
prerun_command: Option<Spanned<String>>,
|
prerun_command: Option<Spanned<String>>,
|
||||||
load_std_lib: Option<Spanned<String>>,
|
load_std_lib: Option<Spanned<String>>,
|
||||||
entire_start_time: Instant,
|
entire_start_time: Instant,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
// throughout this code, we hold this stack uniquely.
|
||||||
|
// During the main REPL loop, we hand ownership of this value to an Arc,
|
||||||
|
// so that it may be read by various reedline plugins. During this, we
|
||||||
|
// can't modify the stack, but at the end of the loop we take back ownership
|
||||||
|
// from the Arc. This lets us avoid copying stack variables needlessly
|
||||||
|
let mut unique_stack = stack;
|
||||||
let config = engine_state.get_config();
|
let config = engine_state.get_config();
|
||||||
let use_color = config.use_ansi_coloring;
|
let use_color = config.use_ansi_coloring;
|
||||||
|
|
||||||
@ -69,7 +74,7 @@ pub fn evaluate_repl(
|
|||||||
|
|
||||||
let start_time = std::time::Instant::now();
|
let start_time = std::time::Instant::now();
|
||||||
// Translate environment variables from Strings to Values
|
// Translate environment variables from Strings to Values
|
||||||
if let Some(e) = convert_env_values(engine_state, stack) {
|
if let Some(e) = convert_env_values(engine_state, &unique_stack) {
|
||||||
report_error_new(engine_state, &e);
|
report_error_new(engine_state, &e);
|
||||||
}
|
}
|
||||||
perf(
|
perf(
|
||||||
@ -82,12 +87,12 @@ pub fn evaluate_repl(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// seed env vars
|
// seed env vars
|
||||||
stack.add_env_var(
|
unique_stack.add_env_var(
|
||||||
"CMD_DURATION_MS".into(),
|
"CMD_DURATION_MS".into(),
|
||||||
Value::string("0823", Span::unknown()),
|
Value::string("0823", Span::unknown()),
|
||||||
);
|
);
|
||||||
|
|
||||||
stack.add_env_var("LAST_EXIT_CODE".into(), Value::int(0, Span::unknown()));
|
unique_stack.add_env_var("LAST_EXIT_CODE".into(), Value::int(0, Span::unknown()));
|
||||||
|
|
||||||
let mut line_editor = get_line_editor(engine_state, nushell_path, use_color)?;
|
let mut line_editor = get_line_editor(engine_state, nushell_path, use_color)?;
|
||||||
let temp_file = temp_dir().join(format!("{}.nu", uuid::Uuid::new_v4()));
|
let temp_file = temp_dir().join(format!("{}.nu", uuid::Uuid::new_v4()));
|
||||||
@ -95,13 +100,14 @@ pub fn evaluate_repl(
|
|||||||
if let Some(s) = prerun_command {
|
if let Some(s) = prerun_command {
|
||||||
eval_source(
|
eval_source(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
&mut unique_stack,
|
||||||
s.item.as_bytes(),
|
s.item.as_bytes(),
|
||||||
&format!("entry #{entry_num}"),
|
&format!("entry #{entry_num}"),
|
||||||
PipelineData::empty(),
|
PipelineData::empty(),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
engine_state.merge_env(stack, get_guaranteed_cwd(engine_state, stack))?;
|
let cwd = get_guaranteed_cwd(engine_state, &unique_stack);
|
||||||
|
engine_state.merge_env(&mut unique_stack, cwd)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
engine_state.set_startup_time(entire_start_time.elapsed().as_nanos() as i64);
|
engine_state.set_startup_time(entire_start_time.elapsed().as_nanos() as i64);
|
||||||
@ -113,7 +119,7 @@ pub fn evaluate_repl(
|
|||||||
if load_std_lib.is_none() && engine_state.get_config().show_banner {
|
if load_std_lib.is_none() && engine_state.get_config().show_banner {
|
||||||
eval_source(
|
eval_source(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
&mut unique_stack,
|
||||||
r#"use std banner; banner"#.as_bytes(),
|
r#"use std banner; banner"#.as_bytes(),
|
||||||
"show_banner",
|
"show_banner",
|
||||||
PipelineData::empty(),
|
PipelineData::empty(),
|
||||||
@ -125,20 +131,22 @@ pub fn evaluate_repl(
|
|||||||
|
|
||||||
// Setup initial engine_state and stack state
|
// Setup initial engine_state and stack state
|
||||||
let mut previous_engine_state = engine_state.clone();
|
let mut previous_engine_state = engine_state.clone();
|
||||||
let mut previous_stack = stack.clone();
|
let mut previous_stack_arc = Arc::new(unique_stack);
|
||||||
loop {
|
loop {
|
||||||
// clone these values so that they can be moved by AssertUnwindSafe
|
// clone these values so that they can be moved by AssertUnwindSafe
|
||||||
// If there is a panic within this iteration the last engine_state and stack
|
// If there is a panic within this iteration the last engine_state and stack
|
||||||
// will be used
|
// will be used
|
||||||
let mut current_engine_state = previous_engine_state.clone();
|
let mut current_engine_state = previous_engine_state.clone();
|
||||||
let mut current_stack = previous_stack.clone();
|
// for the stack, we are going to hold to create a child stack instead,
|
||||||
|
// avoiding an expensive copy
|
||||||
|
let current_stack = Stack::with_parent(previous_stack_arc.clone());
|
||||||
let temp_file_cloned = temp_file.clone();
|
let temp_file_cloned = temp_file.clone();
|
||||||
let mut nu_prompt_cloned = nu_prompt.clone();
|
let mut nu_prompt_cloned = nu_prompt.clone();
|
||||||
|
|
||||||
match catch_unwind(AssertUnwindSafe(move || {
|
match catch_unwind(AssertUnwindSafe(move || {
|
||||||
let (continue_loop, line_editor) = loop_iteration(LoopContext {
|
let (continue_loop, current_stack, line_editor) = loop_iteration(LoopContext {
|
||||||
engine_state: &mut current_engine_state,
|
engine_state: &mut current_engine_state,
|
||||||
stack: &mut current_stack,
|
stack: current_stack,
|
||||||
line_editor,
|
line_editor,
|
||||||
nu_prompt: &mut nu_prompt_cloned,
|
nu_prompt: &mut nu_prompt_cloned,
|
||||||
temp_file: &temp_file_cloned,
|
temp_file: &temp_file_cloned,
|
||||||
@ -157,7 +165,9 @@ pub fn evaluate_repl(
|
|||||||
Ok((continue_loop, es, s, le)) => {
|
Ok((continue_loop, es, s, le)) => {
|
||||||
// setup state for the next iteration of the repl loop
|
// setup state for the next iteration of the repl loop
|
||||||
previous_engine_state = es;
|
previous_engine_state = es;
|
||||||
previous_stack = s;
|
// we apply the changes from the updated stack back onto our previous stack
|
||||||
|
previous_stack_arc =
|
||||||
|
Arc::new(Stack::with_changes_from_child(previous_stack_arc, s));
|
||||||
line_editor = le;
|
line_editor = le;
|
||||||
if !continue_loop {
|
if !continue_loop {
|
||||||
break;
|
break;
|
||||||
@ -211,7 +221,7 @@ fn get_line_editor(
|
|||||||
|
|
||||||
struct LoopContext<'a> {
|
struct LoopContext<'a> {
|
||||||
engine_state: &'a mut EngineState,
|
engine_state: &'a mut EngineState,
|
||||||
stack: &'a mut Stack,
|
stack: Stack,
|
||||||
line_editor: Reedline,
|
line_editor: Reedline,
|
||||||
nu_prompt: &'a mut NushellPrompt,
|
nu_prompt: &'a mut NushellPrompt,
|
||||||
temp_file: &'a Path,
|
temp_file: &'a Path,
|
||||||
@ -222,14 +232,14 @@ struct LoopContext<'a> {
|
|||||||
/// Perform one iteration of the REPL loop
|
/// Perform one iteration of the REPL loop
|
||||||
/// Result is bool: continue loop, current reedline
|
/// Result is bool: continue loop, current reedline
|
||||||
#[inline]
|
#[inline]
|
||||||
fn loop_iteration(ctx: LoopContext) -> (bool, Reedline) {
|
fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||||
use nu_cmd_base::hook;
|
use nu_cmd_base::hook;
|
||||||
use reedline::Signal;
|
use reedline::Signal;
|
||||||
let loop_start_time = std::time::Instant::now();
|
let loop_start_time = std::time::Instant::now();
|
||||||
|
|
||||||
let LoopContext {
|
let LoopContext {
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
mut stack,
|
||||||
line_editor,
|
line_editor,
|
||||||
nu_prompt,
|
nu_prompt,
|
||||||
temp_file,
|
temp_file,
|
||||||
@ -237,12 +247,12 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Reedline) {
|
|||||||
entry_num,
|
entry_num,
|
||||||
} = ctx;
|
} = ctx;
|
||||||
|
|
||||||
let cwd = get_guaranteed_cwd(engine_state, stack);
|
let cwd = get_guaranteed_cwd(engine_state, &stack);
|
||||||
|
|
||||||
let mut start_time = std::time::Instant::now();
|
let mut start_time = std::time::Instant::now();
|
||||||
// Before doing anything, merge the environment from the previous REPL iteration into the
|
// Before doing anything, merge the environment from the previous REPL iteration into the
|
||||||
// permanent state.
|
// permanent state.
|
||||||
if let Err(err) = engine_state.merge_env(stack, cwd) {
|
if let Err(err) = engine_state.merge_env(&mut stack, cwd) {
|
||||||
report_error_new(engine_state, &err);
|
report_error_new(engine_state, &err);
|
||||||
}
|
}
|
||||||
perf(
|
perf(
|
||||||
@ -289,6 +299,10 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Reedline) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
|
// at this line we have cloned the state for the completer and the transient prompt
|
||||||
|
// until we drop those, we cannot use the stack in the REPL loop itself
|
||||||
|
// See STACK-REFERENCE to see where we have taken a reference
|
||||||
|
let mut stack_arc = Arc::new(stack);
|
||||||
|
|
||||||
let mut line_editor = line_editor
|
let mut line_editor = line_editor
|
||||||
.use_kitty_keyboard_enhancement(config.use_kitty_protocol)
|
.use_kitty_keyboard_enhancement(config.use_kitty_protocol)
|
||||||
@ -297,7 +311,8 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Reedline) {
|
|||||||
.use_bracketed_paste(cfg!(not(target_os = "windows")) && config.bracketed_paste)
|
.use_bracketed_paste(cfg!(not(target_os = "windows")) && config.bracketed_paste)
|
||||||
.with_highlighter(Box::new(NuHighlighter {
|
.with_highlighter(Box::new(NuHighlighter {
|
||||||
engine_state: engine_reference.clone(),
|
engine_state: engine_reference.clone(),
|
||||||
stack: std::sync::Arc::new(stack.clone()),
|
// STACK-REFERENCE 1
|
||||||
|
stack: stack_arc.clone(),
|
||||||
config: config.clone(),
|
config: config.clone(),
|
||||||
}))
|
}))
|
||||||
.with_validator(Box::new(NuValidator {
|
.with_validator(Box::new(NuValidator {
|
||||||
@ -305,7 +320,8 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Reedline) {
|
|||||||
}))
|
}))
|
||||||
.with_completer(Box::new(NuCompleter::new(
|
.with_completer(Box::new(NuCompleter::new(
|
||||||
engine_reference.clone(),
|
engine_reference.clone(),
|
||||||
stack.clone(),
|
// STACK-REFERENCE 2
|
||||||
|
Stack::with_parent(stack_arc.clone()),
|
||||||
)))
|
)))
|
||||||
.with_quick_completions(config.quick_completions)
|
.with_quick_completions(config.quick_completions)
|
||||||
.with_partial_completions(config.partial_completions)
|
.with_partial_completions(config.partial_completions)
|
||||||
@ -320,7 +336,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Reedline) {
|
|||||||
use_color,
|
use_color,
|
||||||
);
|
);
|
||||||
|
|
||||||
let style_computer = StyleComputer::from_config(engine_state, stack);
|
let style_computer = StyleComputer::from_config(engine_state, &stack_arc);
|
||||||
|
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
line_editor = if config.use_ansi_coloring {
|
line_editor = if config.use_ansi_coloring {
|
||||||
@ -342,7 +358,8 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Reedline) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
line_editor = add_menus(line_editor, engine_reference, stack, config).unwrap_or_else(|e| {
|
line_editor =
|
||||||
|
add_menus(line_editor, engine_reference, &stack_arc, config).unwrap_or_else(|e| {
|
||||||
report_error_new(engine_state, &e);
|
report_error_new(engine_state, &e);
|
||||||
Reedline::create()
|
Reedline::create()
|
||||||
});
|
});
|
||||||
@ -356,11 +373,11 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Reedline) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
let buffer_editor = get_editor(engine_state, stack, Span::unknown());
|
let buffer_editor = get_editor(engine_state, &stack_arc, Span::unknown());
|
||||||
|
|
||||||
line_editor = if let Ok((cmd, args)) = buffer_editor {
|
line_editor = if let Ok((cmd, args)) = buffer_editor {
|
||||||
let mut command = std::process::Command::new(cmd);
|
let mut command = std::process::Command::new(cmd);
|
||||||
let envs = env_to_strings(engine_state, stack).unwrap_or_else(|e| {
|
let envs = env_to_strings(engine_state, &stack_arc).unwrap_or_else(|e| {
|
||||||
warn!("Couldn't convert environment variable values to strings: {e}");
|
warn!("Couldn't convert environment variable values to strings: {e}");
|
||||||
HashMap::default()
|
HashMap::default()
|
||||||
});
|
});
|
||||||
@ -411,7 +428,14 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Reedline) {
|
|||||||
// Right before we start our prompt and take input from the user,
|
// Right before we start our prompt and take input from the user,
|
||||||
// fire the "pre_prompt" hook
|
// fire the "pre_prompt" hook
|
||||||
if let Some(hook) = config.hooks.pre_prompt.clone() {
|
if let Some(hook) = config.hooks.pre_prompt.clone() {
|
||||||
if let Err(err) = eval_hook(engine_state, stack, None, vec![], &hook, "pre_prompt") {
|
if let Err(err) = eval_hook(
|
||||||
|
engine_state,
|
||||||
|
&mut Stack::with_parent(stack_arc.clone()),
|
||||||
|
None,
|
||||||
|
vec![],
|
||||||
|
&hook,
|
||||||
|
"pre_prompt",
|
||||||
|
) {
|
||||||
report_error_new(engine_state, &err);
|
report_error_new(engine_state, &err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -428,9 +452,11 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Reedline) {
|
|||||||
// Next, check all the environment variables they ask for
|
// Next, check all the environment variables they ask for
|
||||||
// fire the "env_change" hook
|
// fire the "env_change" hook
|
||||||
let config = engine_state.get_config();
|
let config = engine_state.get_config();
|
||||||
if let Err(error) =
|
if let Err(error) = hook::eval_env_change_hook(
|
||||||
hook::eval_env_change_hook(config.hooks.env_change.clone(), engine_state, stack)
|
config.hooks.env_change.clone(),
|
||||||
{
|
engine_state,
|
||||||
|
&mut Stack::with_parent(stack_arc.clone()),
|
||||||
|
) {
|
||||||
report_error_new(engine_state, &error)
|
report_error_new(engine_state, &error)
|
||||||
}
|
}
|
||||||
perf(
|
perf(
|
||||||
@ -444,9 +470,18 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Reedline) {
|
|||||||
|
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
let config = &engine_state.get_config().clone();
|
let config = &engine_state.get_config().clone();
|
||||||
prompt_update::update_prompt(config, engine_state, stack, nu_prompt);
|
prompt_update::update_prompt(
|
||||||
let transient_prompt =
|
config,
|
||||||
prompt_update::make_transient_prompt(config, engine_state, stack, nu_prompt);
|
engine_state,
|
||||||
|
&mut Stack::with_parent(stack_arc.clone()),
|
||||||
|
nu_prompt,
|
||||||
|
);
|
||||||
|
let transient_prompt = prompt_update::make_transient_prompt(
|
||||||
|
config,
|
||||||
|
engine_state,
|
||||||
|
&mut Stack::with_parent(stack_arc.clone()),
|
||||||
|
nu_prompt,
|
||||||
|
);
|
||||||
perf(
|
perf(
|
||||||
"update_prompt",
|
"update_prompt",
|
||||||
start_time,
|
start_time,
|
||||||
@ -461,6 +496,13 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Reedline) {
|
|||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
line_editor = line_editor.with_transient_prompt(transient_prompt);
|
line_editor = line_editor.with_transient_prompt(transient_prompt);
|
||||||
let input = line_editor.read_line(nu_prompt);
|
let input = line_editor.read_line(nu_prompt);
|
||||||
|
// we got our inputs, we can now drop our stack references
|
||||||
|
// This lists all of the stack references that we have cleaned up
|
||||||
|
line_editor = line_editor
|
||||||
|
// CLEAR STACK-REFERENCE 1
|
||||||
|
.with_highlighter(Box::<NoOpHighlighter>::default())
|
||||||
|
// CLEAR STACK-REFERENCE 2
|
||||||
|
.with_completer(Box::<DefaultCompleter>::default());
|
||||||
let shell_integration = config.shell_integration;
|
let shell_integration = config.shell_integration;
|
||||||
|
|
||||||
match input {
|
match input {
|
||||||
@ -483,9 +525,14 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Reedline) {
|
|||||||
repl.buffer = s.to_string();
|
repl.buffer = s.to_string();
|
||||||
drop(repl);
|
drop(repl);
|
||||||
|
|
||||||
if let Err(err) =
|
if let Err(err) = eval_hook(
|
||||||
eval_hook(engine_state, stack, None, vec![], &hook, "pre_execution")
|
engine_state,
|
||||||
{
|
&mut Stack::with_parent(stack_arc.clone()),
|
||||||
|
None,
|
||||||
|
vec![],
|
||||||
|
&hook,
|
||||||
|
"pre_execution",
|
||||||
|
) {
|
||||||
report_error_new(engine_state, &err);
|
report_error_new(engine_state, &err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -502,15 +549,17 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Reedline) {
|
|||||||
// Actual command execution logic starts from here
|
// Actual command execution logic starts from here
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
|
|
||||||
match parse_operation(s.clone(), engine_state, stack) {
|
let mut stack = Stack::unwrap_unique(stack_arc);
|
||||||
|
|
||||||
|
match parse_operation(s.clone(), engine_state, &stack) {
|
||||||
Ok(operation) => match operation {
|
Ok(operation) => match operation {
|
||||||
ReplOperation::AutoCd { cwd, target, span } => {
|
ReplOperation::AutoCd { cwd, target, span } => {
|
||||||
do_auto_cd(target, cwd, stack, engine_state, span);
|
do_auto_cd(target, cwd, &mut stack, engine_state, span);
|
||||||
}
|
}
|
||||||
ReplOperation::RunCommand(cmd) => {
|
ReplOperation::RunCommand(cmd) => {
|
||||||
line_editor = do_run_cmd(
|
line_editor = do_run_cmd(
|
||||||
&cmd,
|
&cmd,
|
||||||
stack,
|
&mut stack,
|
||||||
engine_state,
|
engine_state,
|
||||||
line_editor,
|
line_editor,
|
||||||
shell_integration,
|
shell_integration,
|
||||||
@ -522,7 +571,6 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Reedline) {
|
|||||||
},
|
},
|
||||||
Err(ref e) => error!("Error parsing operation: {e}"),
|
Err(ref e) => error!("Error parsing operation: {e}"),
|
||||||
}
|
}
|
||||||
|
|
||||||
let cmd_duration = start_time.elapsed();
|
let cmd_duration = start_time.elapsed();
|
||||||
|
|
||||||
stack.add_env_var(
|
stack.add_env_var(
|
||||||
@ -535,7 +583,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Reedline) {
|
|||||||
&s,
|
&s,
|
||||||
engine_state,
|
engine_state,
|
||||||
cmd_duration,
|
cmd_duration,
|
||||||
stack,
|
&mut stack,
|
||||||
&mut line_editor,
|
&mut line_editor,
|
||||||
) {
|
) {
|
||||||
warn!("Could not fill in result related history metadata: {e}");
|
warn!("Could not fill in result related history metadata: {e}");
|
||||||
@ -543,24 +591,26 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Reedline) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if shell_integration {
|
if shell_integration {
|
||||||
do_shell_integration_finalize_command(hostname, engine_state, stack);
|
do_shell_integration_finalize_command(hostname, engine_state, &mut stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
flush_engine_state_repl_buffer(engine_state, &mut line_editor);
|
flush_engine_state_repl_buffer(engine_state, &mut line_editor);
|
||||||
|
// put the stack back into the arc
|
||||||
|
stack_arc = Arc::new(stack);
|
||||||
}
|
}
|
||||||
Ok(Signal::CtrlC) => {
|
Ok(Signal::CtrlC) => {
|
||||||
// `Reedline` clears the line content. New prompt is shown
|
// `Reedline` clears the line content. New prompt is shown
|
||||||
if shell_integration {
|
if shell_integration {
|
||||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state));
|
run_ansi_sequence(&get_command_finished_marker(&stack_arc, engine_state));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Signal::CtrlD) => {
|
Ok(Signal::CtrlD) => {
|
||||||
// When exiting clear to a new line
|
// When exiting clear to a new line
|
||||||
if shell_integration {
|
if shell_integration {
|
||||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state));
|
run_ansi_sequence(&get_command_finished_marker(&stack_arc, engine_state));
|
||||||
}
|
}
|
||||||
println!();
|
println!();
|
||||||
return (false, line_editor);
|
return (false, Stack::unwrap_unique(stack_arc), line_editor);
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let message = err.to_string();
|
let message = err.to_string();
|
||||||
@ -572,7 +622,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Reedline) {
|
|||||||
// Alternatively only allow that expected failures let the REPL loop
|
// Alternatively only allow that expected failures let the REPL loop
|
||||||
}
|
}
|
||||||
if shell_integration {
|
if shell_integration {
|
||||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state));
|
run_ansi_sequence(&get_command_finished_marker(&stack_arc, engine_state));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -594,7 +644,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Reedline) {
|
|||||||
use_color,
|
use_color,
|
||||||
);
|
);
|
||||||
|
|
||||||
(true, line_editor)
|
(true, Stack::unwrap_unique(stack_arc), line_editor)
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
@ -99,7 +99,7 @@ fn get_editor_commandline(
|
|||||||
|
|
||||||
pub fn get_editor(
|
pub fn get_editor(
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &Stack,
|
||||||
span: Span,
|
span: Span,
|
||||||
) -> Result<(String, Vec<String>), ShellError> {
|
) -> Result<(String, Vec<String>), ShellError> {
|
||||||
let config = engine_state.get_config();
|
let config = engine_state.get_config();
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::engine::EngineState;
|
use crate::engine::EngineState;
|
||||||
use crate::engine::DEFAULT_OVERLAY_NAME;
|
use crate::engine::DEFAULT_OVERLAY_NAME;
|
||||||
@ -36,6 +37,10 @@ pub struct Stack {
|
|||||||
/// List of active overlays
|
/// List of active overlays
|
||||||
pub active_overlays: Vec<String>,
|
pub active_overlays: Vec<String>,
|
||||||
pub recursion_count: u64,
|
pub recursion_count: u64,
|
||||||
|
|
||||||
|
pub parent_stack: Option<Arc<Stack>>,
|
||||||
|
/// Variables that have been deleted (this is used to hide values from parent stack lookups)
|
||||||
|
pub parent_deletions: Vec<VarId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stack {
|
impl Stack {
|
||||||
@ -46,9 +51,66 @@ impl Stack {
|
|||||||
env_hidden: HashMap::new(),
|
env_hidden: HashMap::new(),
|
||||||
active_overlays: vec![DEFAULT_OVERLAY_NAME.to_string()],
|
active_overlays: vec![DEFAULT_OVERLAY_NAME.to_string()],
|
||||||
recursion_count: 0,
|
recursion_count: 0,
|
||||||
|
parent_stack: None,
|
||||||
|
parent_deletions: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Unwrap a uniquely-owned stack.
|
||||||
|
///
|
||||||
|
/// In debug mode, this panics if there are multiple references.
|
||||||
|
/// In production this will instead clone the underlying stack.
|
||||||
|
pub fn unwrap_unique(stack_arc: Arc<Stack>) -> Stack {
|
||||||
|
// If you hit an error here, it's likely that you created an extra
|
||||||
|
// Arc pointing to the stack somewhere. Make sure that it gets dropped before
|
||||||
|
// getting here!
|
||||||
|
Arc::try_unwrap(stack_arc).unwrap_or_else(|arc| {
|
||||||
|
// in release mode, we clone the stack, but this can lead to
|
||||||
|
// major performance issues, so we should avoid it
|
||||||
|
debug_assert!(false, "More than one stack reference remaining!");
|
||||||
|
(*arc).clone()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/// Create a new child stack from a parent.
|
||||||
|
///
|
||||||
|
/// Changes from this child can be merged back into the parent with
|
||||||
|
/// Stack::with_changes_from_child
|
||||||
|
pub fn with_parent(parent: Arc<Stack>) -> Stack {
|
||||||
|
Stack {
|
||||||
|
// here we are still cloning environment variable-related information
|
||||||
|
env_vars: parent.env_vars.clone(),
|
||||||
|
env_hidden: parent.env_hidden.clone(),
|
||||||
|
active_overlays: parent.active_overlays.clone(),
|
||||||
|
recursion_count: parent.recursion_count,
|
||||||
|
parent_stack: Some(parent),
|
||||||
|
vars: vec![],
|
||||||
|
parent_deletions: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Take an Arc of a parent (assumed to be unique), and a child, and apply
|
||||||
|
/// all the changes from a child back to the parent.
|
||||||
|
///
|
||||||
|
/// Here it is assumed that child was created with a call to Stack::with_parent
|
||||||
|
/// with parent
|
||||||
|
pub fn with_changes_from_child(parent: Arc<Stack>, mut child: Stack) -> Stack {
|
||||||
|
// we're going to drop the link to the parent stack on our new stack
|
||||||
|
// so that we can unwrap the Arc as a unique reference
|
||||||
|
//
|
||||||
|
// This makes the new_stack be in a bit of a weird state, so we shouldn't call
|
||||||
|
// any structs
|
||||||
|
drop(child.parent_stack);
|
||||||
|
let mut unique_stack = Stack::unwrap_unique(parent);
|
||||||
|
|
||||||
|
unique_stack.vars.append(&mut child.vars);
|
||||||
|
unique_stack.env_vars = child.env_vars;
|
||||||
|
unique_stack.env_hidden = child.env_hidden;
|
||||||
|
unique_stack.active_overlays = child.active_overlays;
|
||||||
|
for item in child.parent_deletions.into_iter() {
|
||||||
|
unique_stack.vars.remove(item);
|
||||||
|
}
|
||||||
|
unique_stack
|
||||||
|
}
|
||||||
pub fn with_env(
|
pub fn with_env(
|
||||||
&mut self,
|
&mut self,
|
||||||
env_vars: &[EnvVars],
|
env_vars: &[EnvVars],
|
||||||
@ -64,23 +126,40 @@ impl Stack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Lookup a variable, returning None if it is not present
|
||||||
|
fn lookup_var(&self, var_id: VarId) -> Option<Value> {
|
||||||
|
for (id, val) in &self.vars {
|
||||||
|
if var_id == *id {
|
||||||
|
return Some(val.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(stack) = &self.parent_stack {
|
||||||
|
if !self.parent_deletions.contains(&var_id) {
|
||||||
|
return stack.lookup_var(var_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lookup a variable, erroring if it is not found
|
||||||
|
///
|
||||||
|
/// The passed-in span will be used to tag the value
|
||||||
pub fn get_var(&self, var_id: VarId, span: Span) -> Result<Value, ShellError> {
|
pub fn get_var(&self, var_id: VarId, span: Span) -> Result<Value, ShellError> {
|
||||||
for (id, val) in &self.vars {
|
match self.lookup_var(var_id) {
|
||||||
if var_id == *id {
|
Some(v) => Ok(v.with_span(span)),
|
||||||
return Ok(val.clone().with_span(span));
|
None => Err(ShellError::VariableNotFoundAtRuntime { span }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(ShellError::VariableNotFoundAtRuntime { span })
|
/// Lookup a variable, erroring if it is not found
|
||||||
}
|
///
|
||||||
|
/// While the passed-in span will be used for errors, the returned value
|
||||||
|
/// has the span from where it was originally defined
|
||||||
pub fn get_var_with_origin(&self, var_id: VarId, span: Span) -> Result<Value, ShellError> {
|
pub fn get_var_with_origin(&self, var_id: VarId, span: Span) -> Result<Value, ShellError> {
|
||||||
for (id, val) in &self.vars {
|
match self.lookup_var(var_id) {
|
||||||
if var_id == *id {
|
Some(v) => Ok(v),
|
||||||
return Ok(val.clone());
|
None => {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if var_id == NU_VARIABLE_ID || var_id == ENV_VARIABLE_ID {
|
if var_id == NU_VARIABLE_ID || var_id == ENV_VARIABLE_ID {
|
||||||
return Err(ShellError::GenericError {
|
return Err(ShellError::GenericError {
|
||||||
error: "Built-in variables `$env` and `$nu` have no metadata".into(),
|
error: "Built-in variables `$env` and `$nu` have no metadata".into(),
|
||||||
@ -90,9 +169,10 @@ impl Stack {
|
|||||||
inner: vec![],
|
inner: vec![],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(ShellError::VariableNotFoundAtRuntime { span })
|
Err(ShellError::VariableNotFoundAtRuntime { span })
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_var(&mut self, var_id: VarId, value: Value) {
|
pub fn add_var(&mut self, var_id: VarId, value: Value) {
|
||||||
//self.vars.insert(var_id, value);
|
//self.vars.insert(var_id, value);
|
||||||
@ -109,9 +189,14 @@ impl Stack {
|
|||||||
for (idx, (id, _)) in self.vars.iter().enumerate() {
|
for (idx, (id, _)) in self.vars.iter().enumerate() {
|
||||||
if *id == var_id {
|
if *id == var_id {
|
||||||
self.vars.remove(idx);
|
self.vars.remove(idx);
|
||||||
return;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// even if we did have it in the original layer, we need to make sure to remove it here
|
||||||
|
// as well (since the previous update might have simply hid the parent value)
|
||||||
|
if self.parent_stack.is_some() {
|
||||||
|
self.parent_deletions.push(var_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_env_var(&mut self, var: String, value: Value) {
|
pub fn add_env_var(&mut self, var: String, value: Value) {
|
||||||
@ -160,6 +245,8 @@ impl Stack {
|
|||||||
env_hidden: self.env_hidden.clone(),
|
env_hidden: self.env_hidden.clone(),
|
||||||
active_overlays: self.active_overlays.clone(),
|
active_overlays: self.active_overlays.clone(),
|
||||||
recursion_count: self.recursion_count,
|
recursion_count: self.recursion_count,
|
||||||
|
parent_stack: None,
|
||||||
|
parent_deletions: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,6 +274,8 @@ impl Stack {
|
|||||||
env_hidden: self.env_hidden.clone(),
|
env_hidden: self.env_hidden.clone(),
|
||||||
active_overlays: self.active_overlays.clone(),
|
active_overlays: self.active_overlays.clone(),
|
||||||
recursion_count: self.recursion_count,
|
recursion_count: self.recursion_count,
|
||||||
|
parent_stack: None,
|
||||||
|
parent_deletions: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,7 +397,6 @@ impl Stack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -400,3 +488,107 @@ impl Default for Stack {
|
|||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::{engine::EngineState, Span, Value};
|
||||||
|
|
||||||
|
use super::Stack;
|
||||||
|
|
||||||
|
const ZERO_SPAN: Span = Span { start: 0, end: 0 };
|
||||||
|
fn string_value(s: &str) -> Value {
|
||||||
|
Value::String {
|
||||||
|
val: s.to_string(),
|
||||||
|
internal_span: ZERO_SPAN,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_children_see_inner_values() {
|
||||||
|
let mut original = Stack::new();
|
||||||
|
original.add_var(0, string_value("hello"));
|
||||||
|
|
||||||
|
let cloned = Stack::with_parent(Arc::new(original));
|
||||||
|
assert_eq!(cloned.get_var(0, ZERO_SPAN), Ok(string_value("hello")));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_children_dont_see_deleted_values() {
|
||||||
|
let mut original = Stack::new();
|
||||||
|
original.add_var(0, string_value("hello"));
|
||||||
|
|
||||||
|
let mut cloned = Stack::with_parent(Arc::new(original));
|
||||||
|
cloned.remove_var(0);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
cloned.get_var(0, ZERO_SPAN),
|
||||||
|
Err(crate::ShellError::VariableNotFoundAtRuntime { span: ZERO_SPAN })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_children_changes_override_parent() {
|
||||||
|
let mut original = Stack::new();
|
||||||
|
original.add_var(0, string_value("hello"));
|
||||||
|
|
||||||
|
let mut cloned = Stack::with_parent(Arc::new(original));
|
||||||
|
cloned.add_var(0, string_value("there"));
|
||||||
|
assert_eq!(cloned.get_var(0, ZERO_SPAN), Ok(string_value("there")));
|
||||||
|
|
||||||
|
cloned.remove_var(0);
|
||||||
|
// the underlying value shouldn't magically re-appear
|
||||||
|
assert_eq!(
|
||||||
|
cloned.get_var(0, ZERO_SPAN),
|
||||||
|
Err(crate::ShellError::VariableNotFoundAtRuntime { span: ZERO_SPAN })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_children_changes_persist_in_offspring() {
|
||||||
|
let mut original = Stack::new();
|
||||||
|
original.add_var(0, string_value("hello"));
|
||||||
|
|
||||||
|
let mut cloned = Stack::with_parent(Arc::new(original));
|
||||||
|
cloned.add_var(1, string_value("there"));
|
||||||
|
|
||||||
|
cloned.remove_var(0);
|
||||||
|
let cloned = Stack::with_parent(Arc::new(cloned));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
cloned.get_var(0, ZERO_SPAN),
|
||||||
|
Err(crate::ShellError::VariableNotFoundAtRuntime { span: ZERO_SPAN })
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(cloned.get_var(1, ZERO_SPAN), Ok(string_value("there")));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_merging_children_back_to_parent() {
|
||||||
|
let mut original = Stack::new();
|
||||||
|
let engine_state = EngineState::new();
|
||||||
|
original.add_var(0, string_value("hello"));
|
||||||
|
|
||||||
|
let original_arc = Arc::new(original);
|
||||||
|
let mut cloned = Stack::with_parent(original_arc.clone());
|
||||||
|
cloned.add_var(1, string_value("there"));
|
||||||
|
|
||||||
|
cloned.remove_var(0);
|
||||||
|
|
||||||
|
cloned.add_env_var("ADDED_IN_CHILD".to_string(), string_value("New Env Var"));
|
||||||
|
|
||||||
|
let original = Stack::with_changes_from_child(original_arc, cloned);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
original.get_var(0, ZERO_SPAN),
|
||||||
|
Err(crate::ShellError::VariableNotFoundAtRuntime { span: ZERO_SPAN })
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(original.get_var(1, ZERO_SPAN), Ok(string_value("there")));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
original.get_env_var(&engine_state, "ADDED_IN_CHILD"),
|
||||||
|
Some(string_value("New Env Var")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -9,7 +9,7 @@ use crate::{
|
|||||||
/// The fundamental error type for the evaluation engine. These cases represent different kinds of errors
|
/// The fundamental error type for the evaluation engine. These cases represent different kinds of errors
|
||||||
/// the evaluator might face, along with helpful spans to label. An error renderer will take this error value
|
/// the evaluator might face, along with helpful spans to label. An error renderer will take this error value
|
||||||
/// and pass it into an error viewer to display to the user.
|
/// and pass it into an error viewer to display to the user.
|
||||||
#[derive(Debug, Clone, Error, Diagnostic, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Error, Diagnostic, Serialize, Deserialize, PartialEq)]
|
||||||
pub enum ShellError {
|
pub enum ShellError {
|
||||||
/// An operator received two arguments of incompatible types.
|
/// An operator received two arguments of incompatible types.
|
||||||
///
|
///
|
||||||
|
@ -272,7 +272,7 @@ pub(crate) fn run_repl(
|
|||||||
let start_time = std::time::Instant::now();
|
let start_time = std::time::Instant::now();
|
||||||
let ret_val = evaluate_repl(
|
let ret_val = evaluate_repl(
|
||||||
engine_state,
|
engine_state,
|
||||||
&mut stack,
|
stack,
|
||||||
config_files::NUSHELL_FOLDER,
|
config_files::NUSHELL_FOLDER,
|
||||||
parsed_nu_cli_args.execute,
|
parsed_nu_cli_args.execute,
|
||||||
parsed_nu_cli_args.no_std_lib,
|
parsed_nu_cli_args.no_std_lib,
|
||||||
|
Loading…
Reference in New Issue
Block a user