mirror of
https://github.com/nushell/nushell.git
synced 2024-11-22 16:33:37 +01:00
Handling errors instead of killing the REPL (#11953)
Handle all errors that happen within the REPL loop, display warning or error messages, and return defaults where necessary. This addresses @IanManske [Comment Item 1](https://github.com/nushell/nushell/pull/11860#issuecomment-1959947240) in #11860 --------- Co-authored-by: Jack Wright <jack.wright@disqo.com>
This commit is contained in:
parent
67a63162b2
commit
995989dad4
@ -6,7 +6,7 @@ use crate::{
|
|||||||
NuHighlighter, NuValidator, NushellPrompt,
|
NuHighlighter, NuValidator, NushellPrompt,
|
||||||
};
|
};
|
||||||
use crossterm::cursor::SetCursorStyle;
|
use crossterm::cursor::SetCursorStyle;
|
||||||
use log::{trace, warn};
|
use log::{error, trace, warn};
|
||||||
use miette::{ErrReport, IntoDiagnostic, Result};
|
use miette::{ErrReport, IntoDiagnostic, Result};
|
||||||
use nu_cmd_base::util::get_guaranteed_cwd;
|
use nu_cmd_base::util::get_guaranteed_cwd;
|
||||||
use nu_cmd_base::{hook::eval_hook, util::get_editor};
|
use nu_cmd_base::{hook::eval_hook, util::get_editor};
|
||||||
@ -26,6 +26,7 @@ use reedline::{
|
|||||||
Reedline, SqliteBackedHistory, Vi,
|
Reedline, SqliteBackedHistory, Vi,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
env::temp_dir,
|
env::temp_dir,
|
||||||
io::{self, IsTerminal, Write},
|
io::{self, IsTerminal, Write},
|
||||||
panic::{catch_unwind, AssertUnwindSafe},
|
panic::{catch_unwind, AssertUnwindSafe},
|
||||||
@ -135,47 +136,32 @@ pub fn evaluate_repl(
|
|||||||
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 || {
|
||||||
match loop_iteration(
|
let (continue_loop, line_editor) = loop_iteration(LoopContext {
|
||||||
&mut current_engine_state,
|
engine_state: &mut current_engine_state,
|
||||||
&mut current_stack,
|
stack: &mut current_stack,
|
||||||
line_editor,
|
line_editor,
|
||||||
&mut nu_prompt_cloned,
|
nu_prompt: &mut nu_prompt_cloned,
|
||||||
&temp_file_cloned,
|
temp_file: &temp_file_cloned,
|
||||||
use_color,
|
use_color,
|
||||||
&mut entry_num,
|
entry_num: &mut entry_num,
|
||||||
) {
|
});
|
||||||
|
|
||||||
// pass the most recent version of the line_editor back
|
// pass the most recent version of the line_editor back
|
||||||
Ok((continue_loop, line_editor)) => (
|
(
|
||||||
Ok(continue_loop),
|
continue_loop,
|
||||||
current_engine_state,
|
current_engine_state,
|
||||||
current_stack,
|
current_stack,
|
||||||
line_editor,
|
line_editor,
|
||||||
),
|
|
||||||
Err(e) => {
|
|
||||||
current_engine_state.recover_from_panic();
|
|
||||||
(
|
|
||||||
Err(e),
|
|
||||||
current_engine_state,
|
|
||||||
current_stack,
|
|
||||||
Reedline::create(),
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
})) {
|
})) {
|
||||||
Ok((result, 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;
|
previous_stack = s;
|
||||||
line_editor = le;
|
line_editor = le;
|
||||||
match result {
|
if !continue_loop {
|
||||||
Ok(false) => {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// line_editor is lost in the error case so reconstruct a new one
|
// line_editor is lost in the error case so reconstruct a new one
|
||||||
@ -223,23 +209,34 @@ fn get_line_editor(
|
|||||||
Ok(line_editor)
|
Ok(line_editor)
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
struct LoopContext<'a> {
|
||||||
|
engine_state: &'a mut EngineState,
|
||||||
|
stack: &'a mut Stack,
|
||||||
|
line_editor: Reedline,
|
||||||
|
nu_prompt: &'a mut NushellPrompt,
|
||||||
|
temp_file: &'a Path,
|
||||||
|
use_color: bool,
|
||||||
|
entry_num: &'a mut usize,
|
||||||
|
}
|
||||||
|
|
||||||
/// 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(
|
fn loop_iteration(ctx: LoopContext) -> (bool, Reedline) {
|
||||||
engine_state: &mut EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
line_editor: Reedline,
|
|
||||||
nu_prompt: &mut NushellPrompt,
|
|
||||||
temp_file: &Path,
|
|
||||||
use_color: bool,
|
|
||||||
entry_num: &mut usize,
|
|
||||||
) -> Result<(bool, 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 {
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
line_editor,
|
||||||
|
nu_prompt,
|
||||||
|
temp_file,
|
||||||
|
use_color,
|
||||||
|
entry_num,
|
||||||
|
} = 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();
|
||||||
@ -363,9 +360,11 @@ fn loop_iteration(
|
|||||||
|
|
||||||
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);
|
||||||
command
|
let envs = env_to_strings(engine_state, stack).unwrap_or_else(|e| {
|
||||||
.args(args)
|
warn!("Couldn't convert environment variable values to strings: {e}");
|
||||||
.envs(env_to_strings(engine_state, stack)?);
|
HashMap::default()
|
||||||
|
});
|
||||||
|
command.args(args).envs(envs);
|
||||||
line_editor.with_buffer_editor(command, temp_file.to_path_buf())
|
line_editor.with_buffer_editor(command, temp_file.to_path_buf())
|
||||||
} else {
|
} else {
|
||||||
line_editor
|
line_editor
|
||||||
@ -473,7 +472,7 @@ fn loop_iteration(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if history_supports_meta {
|
if history_supports_meta {
|
||||||
prepare_history_metadata(&s, &hostname, engine_state, &mut line_editor)?;
|
prepare_history_metadata(&s, &hostname, engine_state, &mut line_editor);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Right before we start running the code the user gave us, fire the `pre_execution`
|
// Right before we start running the code the user gave us, fire the `pre_execution`
|
||||||
@ -497,13 +496,14 @@ fn loop_iteration(
|
|||||||
drop(repl);
|
drop(repl);
|
||||||
|
|
||||||
if shell_integration {
|
if shell_integration {
|
||||||
run_ansi_sequence(PRE_EXECUTE_MARKER)?;
|
run_ansi_sequence(PRE_EXECUTE_MARKER);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)? {
|
match parse_operation(s.clone(), engine_state, stack) {
|
||||||
|
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, stack, engine_state, span);
|
||||||
}
|
}
|
||||||
@ -515,10 +515,12 @@ fn loop_iteration(
|
|||||||
line_editor,
|
line_editor,
|
||||||
shell_integration,
|
shell_integration,
|
||||||
*entry_num,
|
*entry_num,
|
||||||
)?;
|
)
|
||||||
}
|
}
|
||||||
// as the name implies, we do nothing in this case
|
// as the name implies, we do nothing in this case
|
||||||
ReplOperation::DoNothing => {}
|
ReplOperation::DoNothing => {}
|
||||||
|
},
|
||||||
|
Err(ref e) => error!("Error parsing operation: {e}"),
|
||||||
}
|
}
|
||||||
|
|
||||||
let cmd_duration = start_time.elapsed();
|
let cmd_duration = start_time.elapsed();
|
||||||
@ -529,17 +531,19 @@ fn loop_iteration(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if history_supports_meta {
|
if history_supports_meta {
|
||||||
fill_in_result_related_history_metadata(
|
if let Err(e) = fill_in_result_related_history_metadata(
|
||||||
&s,
|
&s,
|
||||||
engine_state,
|
engine_state,
|
||||||
cmd_duration,
|
cmd_duration,
|
||||||
stack,
|
stack,
|
||||||
&mut line_editor,
|
&mut line_editor,
|
||||||
)?;
|
) {
|
||||||
|
warn!("Could not fill in result related history metadata: {e}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if shell_integration {
|
if shell_integration {
|
||||||
do_shell_integration_finalize_command(hostname, engine_state, stack)?;
|
do_shell_integration_finalize_command(hostname, engine_state, stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
flush_engine_state_repl_buffer(engine_state, &mut line_editor);
|
flush_engine_state_repl_buffer(engine_state, &mut line_editor);
|
||||||
@ -547,16 +551,16 @@ fn loop_iteration(
|
|||||||
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, 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, engine_state));
|
||||||
}
|
}
|
||||||
println!();
|
println!();
|
||||||
return Ok((false, line_editor));
|
return (false, line_editor);
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let message = err.to_string();
|
let message = err.to_string();
|
||||||
@ -568,7 +572,7 @@ fn loop_iteration(
|
|||||||
// 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, engine_state));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -590,7 +594,7 @@ fn loop_iteration(
|
|||||||
use_color,
|
use_color,
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok((true, line_editor))
|
(true, line_editor)
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@ -601,9 +605,9 @@ fn prepare_history_metadata(
|
|||||||
hostname: &Option<String>,
|
hostname: &Option<String>,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
line_editor: &mut Reedline,
|
line_editor: &mut Reedline,
|
||||||
) -> Result<()> {
|
) {
|
||||||
if !s.is_empty() && line_editor.has_last_command_context() {
|
if !s.is_empty() && line_editor.has_last_command_context() {
|
||||||
line_editor
|
let result = line_editor
|
||||||
.update_last_command_context(&|mut c| {
|
.update_last_command_context(&|mut c| {
|
||||||
c.start_timestamp = Some(chrono::Utc::now());
|
c.start_timestamp = Some(chrono::Utc::now());
|
||||||
c.hostname = hostname.clone();
|
c.hostname = hostname.clone();
|
||||||
@ -611,9 +615,11 @@ fn prepare_history_metadata(
|
|||||||
c.cwd = Some(StateWorkingSet::new(engine_state).get_cwd());
|
c.cwd = Some(StateWorkingSet::new(engine_state).get_cwd());
|
||||||
c
|
c
|
||||||
})
|
})
|
||||||
.into_diagnostic()?; // todo: don't stop repl if error here?
|
.into_diagnostic();
|
||||||
|
if let Err(e) = result {
|
||||||
|
warn!("Could not prepare history metadata: {e}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@ -766,7 +772,7 @@ fn do_run_cmd(
|
|||||||
line_editor: Reedline,
|
line_editor: Reedline,
|
||||||
shell_integration: bool,
|
shell_integration: bool,
|
||||||
entry_num: usize,
|
entry_num: usize,
|
||||||
) -> Result<Reedline> {
|
) -> Reedline {
|
||||||
trace!("eval source: {}", s);
|
trace!("eval source: {}", s);
|
||||||
|
|
||||||
let mut cmds = s.split_whitespace();
|
let mut cmds = s.split_whitespace();
|
||||||
@ -792,8 +798,8 @@ fn do_run_cmd(
|
|||||||
|
|
||||||
if shell_integration {
|
if shell_integration {
|
||||||
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
||||||
let path = cwd.coerce_into_string()?;
|
match cwd.coerce_into_string() {
|
||||||
|
Ok(path) => {
|
||||||
// Try to abbreviate string for windows title
|
// Try to abbreviate string for windows title
|
||||||
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
|
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
|
||||||
path.replace(&p.as_path().display().to_string(), "~")
|
path.replace(&p.as_path().display().to_string(), "~")
|
||||||
@ -803,7 +809,14 @@ fn do_run_cmd(
|
|||||||
let binary_name = s.split_whitespace().next();
|
let binary_name = s.split_whitespace().next();
|
||||||
|
|
||||||
if let Some(binary_name) = binary_name {
|
if let Some(binary_name) = binary_name {
|
||||||
run_ansi_sequence(&format!("\x1b]2;{maybe_abbrev_path}> {binary_name}\x07"))?;
|
run_ansi_sequence(&format!(
|
||||||
|
"\x1b]2;{maybe_abbrev_path}> {binary_name}\x07"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Could not coerce working directory to string {e}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -817,7 +830,7 @@ fn do_run_cmd(
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(line_editor)
|
line_editor
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@ -828,17 +841,19 @@ fn do_shell_integration_finalize_command(
|
|||||||
hostname: Option<String>,
|
hostname: Option<String>,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
) -> Result<()> {
|
) {
|
||||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
run_ansi_sequence(&get_command_finished_marker(stack, engine_state));
|
||||||
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
||||||
let path = cwd.coerce_into_string()?;
|
match cwd.coerce_into_string() {
|
||||||
|
Ok(path) => {
|
||||||
// Supported escape sequences of Microsoft's Visual Studio Code (vscode)
|
// Supported escape sequences of Microsoft's Visual Studio Code (vscode)
|
||||||
// https://code.visualstudio.com/docs/terminal/shell-integration#_supported-escape-sequences
|
// https://code.visualstudio.com/docs/terminal/shell-integration#_supported-escape-sequences
|
||||||
if stack.get_env_var(engine_state, "TERM_PROGRAM") == Some(Value::test_string("vscode")) {
|
if stack.get_env_var(engine_state, "TERM_PROGRAM")
|
||||||
|
== Some(Value::test_string("vscode"))
|
||||||
|
{
|
||||||
// If we're in vscode, run their specific ansi escape sequence.
|
// If we're in vscode, run their specific ansi escape sequence.
|
||||||
// This is helpful for ctrl+g to change directories in the terminal.
|
// This is helpful for ctrl+g to change directories in the terminal.
|
||||||
run_ansi_sequence(&format!("\x1b]633;P;Cwd={}\x1b\\", path))?;
|
run_ansi_sequence(&format!("\x1b]633;P;Cwd={}\x1b\\", path));
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, communicate the path as OSC 7 (often used for spawning new tabs in the same dir)
|
// Otherwise, communicate the path as OSC 7 (often used for spawning new tabs in the same dir)
|
||||||
run_ansi_sequence(&format!(
|
run_ansi_sequence(&format!(
|
||||||
@ -849,7 +864,7 @@ fn do_shell_integration_finalize_command(
|
|||||||
),
|
),
|
||||||
if path.starts_with('/') { "" } else { "/" },
|
if path.starts_with('/') { "" } else { "/" },
|
||||||
percent_encoding::utf8_percent_encode(&path, percent_encoding::CONTROLS)
|
percent_encoding::utf8_percent_encode(&path, percent_encoding::CONTROLS)
|
||||||
))?;
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to abbreviate string for windows title
|
// Try to abbreviate string for windows title
|
||||||
@ -864,10 +879,14 @@ fn do_shell_integration_finalize_command(
|
|||||||
// ESC]0;stringBEL -- Set icon name and window title to string
|
// ESC]0;stringBEL -- Set icon name and window title to string
|
||||||
// ESC]1;stringBEL -- Set icon name to string
|
// ESC]1;stringBEL -- Set icon name to string
|
||||||
// ESC]2;stringBEL -- Set window title to string
|
// ESC]2;stringBEL -- Set window title to string
|
||||||
run_ansi_sequence(&format!("\x1b]2;{maybe_abbrev_path}\x07"))?;
|
run_ansi_sequence(&format!("\x1b]2;{maybe_abbrev_path}\x07"));
|
||||||
}
|
}
|
||||||
run_ansi_sequence(RESET_APPLICATION_MODE)?;
|
Err(e) => {
|
||||||
Ok(())
|
warn!("Could not coerce working directory to string {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run_ansi_sequence(RESET_APPLICATION_MODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@ -1021,23 +1040,12 @@ fn get_command_finished_marker(stack: &Stack, engine_state: &EngineState) -> Str
|
|||||||
format!("\x1b]133;D;{}\x1b\\", exit_code.unwrap_or(0))
|
format!("\x1b]133;D;{}\x1b\\", exit_code.unwrap_or(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_ansi_sequence(seq: &str) -> Result<(), ShellError> {
|
fn run_ansi_sequence(seq: &str) {
|
||||||
io::stdout()
|
if let Err(e) = io::stdout().write_all(seq.as_bytes()) {
|
||||||
.write_all(seq.as_bytes())
|
warn!("Error writing ansi sequence {e}");
|
||||||
.map_err(|e| ShellError::GenericError {
|
} else if let Err(e) = io::stdout().flush() {
|
||||||
error: "Error writing ansi sequence".into(),
|
warn!("Error flushing stdio {e}");
|
||||||
msg: e.to_string(),
|
}
|
||||||
span: Some(Span::unknown()),
|
|
||||||
help: None,
|
|
||||||
inner: vec![],
|
|
||||||
})?;
|
|
||||||
io::stdout().flush().map_err(|e| ShellError::GenericError {
|
|
||||||
error: "Error flushing stdio".into(),
|
|
||||||
msg: e.to_string(),
|
|
||||||
span: Some(Span::unknown()),
|
|
||||||
help: None,
|
|
||||||
inner: vec![],
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Absolute paths with a drive letter, like 'C:', 'D:\', 'E:\foo'
|
// Absolute paths with a drive letter, like 'C:', 'D:\', 'E:\foo'
|
||||||
|
Loading…
Reference in New Issue
Block a user