mirror of
https://github.com/nushell/nushell
synced 2024-11-10 07:04:13 +00:00
wrapping run_repl with catch_unwind and restarting the repl on panic (#11860)
Provides the ability to cleanly recover from panics, falling back to the last known good state of EngineState and Stack. This pull request also utilizes miette's panic handler for better formatting of panics. <img width="642" alt="Screenshot 2024-02-21 at 08 34 35" src="https://github.com/nushell/nushell/assets/56345/f81efaba-aa45-4e47-991c-1a2cf99e06ff"> --------- Co-authored-by: Jack Wright <jack.wright@disqo.com>
This commit is contained in:
parent
cf68334fa0
commit
f17f857b1f
9 changed files with 521 additions and 361 deletions
11
Cargo.lock
generated
11
Cargo.lock
generated
|
@ -324,6 +324,15 @@ dependencies = [
|
|||
"rustc-demangle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace-ext"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.1"
|
||||
|
@ -2552,6 +2561,8 @@ version = "7.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baed61d13cc3723ee6dbed730a82bfacedc60a85d81da2d77e9c3e8ebc0b504a"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"backtrace-ext",
|
||||
"miette-derive",
|
||||
"owo-colors",
|
||||
"supports-color",
|
||||
|
|
|
@ -84,7 +84,7 @@ reedline = { version = "0.29.0", features = ["bashisms", "sqlite"] }
|
|||
crossterm = "0.27"
|
||||
ctrlc = "3.4"
|
||||
log = "0.4"
|
||||
miette = { version = "7.1", features = ["fancy-no-backtrace"] }
|
||||
miette = { version = "7.1", features = ["fancy-no-backtrace", "fancy"] }
|
||||
mimalloc = { version = "0.1.37", default-features = false, optional = true }
|
||||
serde_json = "1.0"
|
||||
simplelog = "0.12"
|
||||
|
|
|
@ -28,6 +28,8 @@ use reedline::{
|
|||
use std::{
|
||||
env::temp_dir,
|
||||
io::{self, IsTerminal, Write},
|
||||
panic::{catch_unwind, AssertUnwindSafe},
|
||||
path::Path,
|
||||
path::PathBuf,
|
||||
sync::atomic::Ordering,
|
||||
time::{Duration, Instant},
|
||||
|
@ -55,8 +57,6 @@ pub fn evaluate_repl(
|
|||
load_std_lib: Option<Spanned<String>>,
|
||||
entire_start_time: Instant,
|
||||
) -> Result<()> {
|
||||
use nu_cmd_base::hook;
|
||||
use reedline::Signal;
|
||||
let config = engine_state.get_config();
|
||||
let use_color = config.use_ansi_coloring;
|
||||
|
||||
|
@ -64,7 +64,7 @@ pub fn evaluate_repl(
|
|||
|
||||
let mut entry_num = 0;
|
||||
|
||||
let mut nu_prompt = NushellPrompt::new(config.shell_integration);
|
||||
let nu_prompt = NushellPrompt::new(config.shell_integration);
|
||||
|
||||
let start_time = std::time::Instant::now();
|
||||
// Translate environment variables from Strings to Values
|
||||
|
@ -88,36 +88,9 @@ pub fn evaluate_repl(
|
|||
|
||||
stack.add_env_var("LAST_EXIT_CODE".into(), Value::int(0, Span::unknown()));
|
||||
|
||||
let mut start_time = std::time::Instant::now();
|
||||
let mut line_editor = Reedline::create();
|
||||
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()));
|
||||
|
||||
// Now that reedline is created, get the history session id and store it in engine_state
|
||||
store_history_id_in_engine(engine_state, &line_editor);
|
||||
perf(
|
||||
"setup reedline",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
if let Some(history) = engine_state.history_config() {
|
||||
start_time = std::time::Instant::now();
|
||||
|
||||
line_editor = setup_history(nushell_path, engine_state, line_editor, history)?;
|
||||
|
||||
perf(
|
||||
"setup history",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(s) = prerun_command {
|
||||
eval_source(
|
||||
engine_state,
|
||||
|
@ -149,353 +122,273 @@ pub fn evaluate_repl(
|
|||
|
||||
kitty_protocol_healthcheck(engine_state);
|
||||
|
||||
// Setup initial engine_state and stack state
|
||||
let mut previous_engine_state = engine_state.clone();
|
||||
let mut previous_stack = stack.clone();
|
||||
loop {
|
||||
let loop_start_time = std::time::Instant::now();
|
||||
// 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
|
||||
// will be used
|
||||
let mut current_engine_state = previous_engine_state.clone();
|
||||
let mut current_stack = previous_stack.clone();
|
||||
let temp_file_cloned = temp_file.clone();
|
||||
let mut nu_prompt_cloned = nu_prompt.clone();
|
||||
|
||||
let cwd = get_guaranteed_cwd(engine_state, stack);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
// Before doing anything, merge the environment from the previous REPL iteration into the
|
||||
// permanent state.
|
||||
if let Err(err) = engine_state.merge_env(stack, cwd) {
|
||||
report_error_new(engine_state, &err);
|
||||
}
|
||||
perf(
|
||||
"merge env",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
//Reset the ctrl-c handler
|
||||
if let Some(ctrlc) = &mut engine_state.ctrlc {
|
||||
ctrlc.store(false, Ordering::SeqCst);
|
||||
}
|
||||
perf(
|
||||
"reset ctrlc",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
let config = engine_state.get_config();
|
||||
|
||||
let engine_reference = std::sync::Arc::new(engine_state.clone());
|
||||
|
||||
// Find the configured cursor shapes for each mode
|
||||
let cursor_config = CursorConfig {
|
||||
vi_insert: map_nucursorshape_to_cursorshape(config.cursor_shape_vi_insert),
|
||||
vi_normal: map_nucursorshape_to_cursorshape(config.cursor_shape_vi_normal),
|
||||
emacs: map_nucursorshape_to_cursorshape(config.cursor_shape_emacs),
|
||||
};
|
||||
perf(
|
||||
"get config/cursor config",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
|
||||
line_editor = line_editor
|
||||
.use_kitty_keyboard_enhancement(config.use_kitty_protocol)
|
||||
// try to enable bracketed paste
|
||||
// It doesn't work on windows system: https://github.com/crossterm-rs/crossterm/issues/737
|
||||
.use_bracketed_paste(cfg!(not(target_os = "windows")) && config.bracketed_paste)
|
||||
.with_highlighter(Box::new(NuHighlighter {
|
||||
engine_state: engine_reference.clone(),
|
||||
stack: std::sync::Arc::new(stack.clone()),
|
||||
config: config.clone(),
|
||||
}))
|
||||
.with_validator(Box::new(NuValidator {
|
||||
engine_state: engine_reference.clone(),
|
||||
}))
|
||||
.with_completer(Box::new(NuCompleter::new(
|
||||
engine_reference.clone(),
|
||||
stack.clone(),
|
||||
)))
|
||||
.with_quick_completions(config.quick_completions)
|
||||
.with_partial_completions(config.partial_completions)
|
||||
.with_ansi_colors(config.use_ansi_coloring)
|
||||
.with_cursor_config(cursor_config);
|
||||
perf(
|
||||
"reedline builder",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
let style_computer = StyleComputer::from_config(engine_state, stack);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
line_editor = if config.use_ansi_coloring {
|
||||
line_editor.with_hinter(Box::new({
|
||||
// As of Nov 2022, "hints" color_config closures only get `null` passed in.
|
||||
let style = style_computer.compute("hints", &Value::nothing(Span::unknown()));
|
||||
CwdAwareHinter::default().with_style(style)
|
||||
}))
|
||||
} else {
|
||||
line_editor.disable_hints()
|
||||
};
|
||||
perf(
|
||||
"reedline coloring/style_computer",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
line_editor = add_menus(line_editor, engine_reference, stack, config).unwrap_or_else(|e| {
|
||||
report_error_new(engine_state, &e);
|
||||
Reedline::create()
|
||||
});
|
||||
perf(
|
||||
"reedline menus",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
let buffer_editor = get_editor(engine_state, stack, Span::unknown());
|
||||
|
||||
line_editor = if let Ok((cmd, args)) = buffer_editor {
|
||||
let mut command = std::process::Command::new(&cmd);
|
||||
command
|
||||
.args(args)
|
||||
.envs(env_to_strings(engine_state, stack)?);
|
||||
line_editor.with_buffer_editor(command, temp_file.clone())
|
||||
} else {
|
||||
line_editor
|
||||
};
|
||||
perf(
|
||||
"reedline buffer_editor",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
if let Some(history) = engine_state.history_config() {
|
||||
start_time = std::time::Instant::now();
|
||||
if history.sync_on_enter {
|
||||
if let Err(e) = line_editor.sync_history() {
|
||||
warn!("Failed to sync history: {}", e);
|
||||
}
|
||||
}
|
||||
perf(
|
||||
"sync_history",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
match catch_unwind(AssertUnwindSafe(move || {
|
||||
match loop_iteration(
|
||||
&mut current_engine_state,
|
||||
&mut current_stack,
|
||||
line_editor,
|
||||
&mut nu_prompt_cloned,
|
||||
&temp_file_cloned,
|
||||
use_color,
|
||||
);
|
||||
}
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
// Changing the line editor based on the found keybindings
|
||||
line_editor = setup_keybindings(engine_state, line_editor);
|
||||
perf(
|
||||
"keybindings",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
// Right before we start our prompt and take input from the user,
|
||||
// fire the "pre_prompt" hook
|
||||
if let Some(hook) = config.hooks.pre_prompt.clone() {
|
||||
if let Err(err) = eval_hook(engine_state, stack, None, vec![], &hook, "pre_prompt") {
|
||||
report_error_new(engine_state, &err);
|
||||
}
|
||||
}
|
||||
perf(
|
||||
"pre-prompt hook",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
// Next, check all the environment variables they ask for
|
||||
// fire the "env_change" hook
|
||||
let config = engine_state.get_config();
|
||||
if let Err(error) =
|
||||
hook::eval_env_change_hook(config.hooks.env_change.clone(), engine_state, stack)
|
||||
{
|
||||
report_error_new(engine_state, &error)
|
||||
}
|
||||
perf(
|
||||
"env-change hook",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
let config = &engine_state.get_config().clone();
|
||||
prompt_update::update_prompt(config, engine_state, stack, &mut nu_prompt);
|
||||
let transient_prompt =
|
||||
prompt_update::make_transient_prompt(config, engine_state, stack, &nu_prompt);
|
||||
perf(
|
||||
"update_prompt",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
entry_num += 1;
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
line_editor = line_editor.with_transient_prompt(transient_prompt);
|
||||
let input = line_editor.read_line(&nu_prompt);
|
||||
let shell_integration = config.shell_integration;
|
||||
|
||||
match input {
|
||||
Ok(Signal::Success(s)) => {
|
||||
let hostname = System::host_name();
|
||||
let history_supports_meta = matches!(
|
||||
engine_state.history_config().map(|h| h.file_format),
|
||||
Some(HistoryFileFormat::Sqlite)
|
||||
);
|
||||
|
||||
if history_supports_meta {
|
||||
prepare_history_metadata(&s, &hostname, engine_state, &mut line_editor)?;
|
||||
&mut entry_num,
|
||||
) {
|
||||
// pass the most recent version of the line_editor back
|
||||
Ok((continue_loop, line_editor)) => (
|
||||
Ok(continue_loop),
|
||||
current_engine_state,
|
||||
current_stack,
|
||||
line_editor,
|
||||
),
|
||||
Err(e) => {
|
||||
current_engine_state.recover_from_panic();
|
||||
(
|
||||
Err(e),
|
||||
current_engine_state,
|
||||
current_stack,
|
||||
Reedline::create(),
|
||||
)
|
||||
}
|
||||
|
||||
// Right before we start running the code the user gave us, fire the `pre_execution`
|
||||
// hook
|
||||
if let Some(hook) = config.hooks.pre_execution.clone() {
|
||||
// Set the REPL buffer to the current command for the "pre_execution" hook
|
||||
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
|
||||
repl.buffer = s.to_string();
|
||||
drop(repl);
|
||||
|
||||
if let Err(err) =
|
||||
eval_hook(engine_state, stack, None, vec![], &hook, "pre_execution")
|
||||
{
|
||||
report_error_new(engine_state, &err);
|
||||
}
|
||||
})) {
|
||||
Ok((result, es, s, le)) => {
|
||||
// setup state for the next iteration of the repl loop
|
||||
previous_engine_state = es;
|
||||
previous_stack = s;
|
||||
line_editor = le;
|
||||
match result {
|
||||
Ok(false) => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
|
||||
repl.cursor_pos = line_editor.current_insertion_point();
|
||||
repl.buffer = line_editor.current_buffer_contents().to_string();
|
||||
drop(repl);
|
||||
|
||||
if shell_integration {
|
||||
run_ansi_sequence(PRE_EXECUTE_MARKER)?;
|
||||
}
|
||||
|
||||
// Actual command execution logic starts from here
|
||||
let start_time = Instant::now();
|
||||
|
||||
match parse_operation(s.clone(), engine_state, stack)? {
|
||||
ReplOperation::AutoCd { cwd, target, span } => {
|
||||
do_auto_cd(target, cwd, stack, engine_state, span);
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
ReplOperation::RunCommand(cmd) => {
|
||||
line_editor = do_run_cmd(
|
||||
&cmd,
|
||||
stack,
|
||||
engine_state,
|
||||
line_editor,
|
||||
shell_integration,
|
||||
entry_num,
|
||||
)?;
|
||||
}
|
||||
// as the name implies, we do nothing in this case
|
||||
ReplOperation::DoNothing => {}
|
||||
}
|
||||
|
||||
let cmd_duration = start_time.elapsed();
|
||||
|
||||
stack.add_env_var(
|
||||
"CMD_DURATION_MS".into(),
|
||||
Value::string(format!("{}", cmd_duration.as_millis()), Span::unknown()),
|
||||
);
|
||||
|
||||
if history_supports_meta {
|
||||
fill_in_result_related_history_metadata(
|
||||
&s,
|
||||
engine_state,
|
||||
cmd_duration,
|
||||
stack,
|
||||
&mut line_editor,
|
||||
)?;
|
||||
}
|
||||
|
||||
if shell_integration {
|
||||
do_shell_integration_finalize_command(hostname, engine_state, stack)?;
|
||||
}
|
||||
|
||||
flush_engine_state_repl_buffer(engine_state, &mut line_editor);
|
||||
}
|
||||
Ok(Signal::CtrlC) => {
|
||||
// `Reedline` clears the line content. New prompt is shown
|
||||
if shell_integration {
|
||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
Ok(Signal::CtrlD) => {
|
||||
// When exiting clear to a new line
|
||||
if shell_integration {
|
||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
||||
}
|
||||
println!();
|
||||
break;
|
||||
}
|
||||
Err(err) => {
|
||||
let message = err.to_string();
|
||||
if !message.contains("duration") {
|
||||
eprintln!("Error: {err:?}");
|
||||
// TODO: Identify possible error cases where a hard failure is preferable
|
||||
// Ignoring and reporting could hide bigger problems
|
||||
// e.g. https://github.com/nushell/nushell/issues/6452
|
||||
// Alternatively only allow that expected failures let the REPL loop
|
||||
}
|
||||
if shell_integration {
|
||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
||||
}
|
||||
Err(_) => {
|
||||
// line_editor is lost in the error case so reconstruct a new one
|
||||
line_editor = get_line_editor(engine_state, nushell_path, use_color)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_line_editor(
|
||||
engine_state: &mut EngineState,
|
||||
nushell_path: &str,
|
||||
use_color: bool,
|
||||
) -> Result<Reedline> {
|
||||
let mut start_time = std::time::Instant::now();
|
||||
let mut line_editor = Reedline::create();
|
||||
|
||||
// Now that reedline is created, get the history session id and store it in engine_state
|
||||
store_history_id_in_engine(engine_state, &line_editor);
|
||||
perf(
|
||||
"setup reedline",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
if let Some(history) = engine_state.history_config() {
|
||||
start_time = std::time::Instant::now();
|
||||
|
||||
line_editor = setup_history(nushell_path, engine_state, line_editor, history)?;
|
||||
|
||||
perf(
|
||||
"processing line editor input",
|
||||
"setup history",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
}
|
||||
Ok(line_editor)
|
||||
}
|
||||
|
||||
///
|
||||
/// Perform one iteration of the REPL loop
|
||||
/// Result is bool: continue loop, current reedline
|
||||
#[inline]
|
||||
fn loop_iteration(
|
||||
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 reedline::Signal;
|
||||
let loop_start_time = std::time::Instant::now();
|
||||
|
||||
let cwd = get_guaranteed_cwd(engine_state, stack);
|
||||
|
||||
let mut start_time = std::time::Instant::now();
|
||||
// Before doing anything, merge the environment from the previous REPL iteration into the
|
||||
// permanent state.
|
||||
if let Err(err) = engine_state.merge_env(stack, cwd) {
|
||||
report_error_new(engine_state, &err);
|
||||
}
|
||||
perf(
|
||||
"merge env",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
//Reset the ctrl-c handler
|
||||
if let Some(ctrlc) = &mut engine_state.ctrlc {
|
||||
ctrlc.store(false, Ordering::SeqCst);
|
||||
}
|
||||
perf(
|
||||
"reset ctrlc",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
let config = engine_state.get_config();
|
||||
|
||||
let engine_reference = std::sync::Arc::new(engine_state.clone());
|
||||
|
||||
// Find the configured cursor shapes for each mode
|
||||
let cursor_config = CursorConfig {
|
||||
vi_insert: map_nucursorshape_to_cursorshape(config.cursor_shape_vi_insert),
|
||||
vi_normal: map_nucursorshape_to_cursorshape(config.cursor_shape_vi_normal),
|
||||
emacs: map_nucursorshape_to_cursorshape(config.cursor_shape_emacs),
|
||||
};
|
||||
perf(
|
||||
"get config/cursor config",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
|
||||
let mut line_editor = line_editor
|
||||
.use_kitty_keyboard_enhancement(config.use_kitty_protocol)
|
||||
// try to enable bracketed paste
|
||||
// It doesn't work on windows system: https://github.com/crossterm-rs/crossterm/issues/737
|
||||
.use_bracketed_paste(cfg!(not(target_os = "windows")) && config.bracketed_paste)
|
||||
.with_highlighter(Box::new(NuHighlighter {
|
||||
engine_state: engine_reference.clone(),
|
||||
stack: std::sync::Arc::new(stack.clone()),
|
||||
config: config.clone(),
|
||||
}))
|
||||
.with_validator(Box::new(NuValidator {
|
||||
engine_state: engine_reference.clone(),
|
||||
}))
|
||||
.with_completer(Box::new(NuCompleter::new(
|
||||
engine_reference.clone(),
|
||||
stack.clone(),
|
||||
)))
|
||||
.with_quick_completions(config.quick_completions)
|
||||
.with_partial_completions(config.partial_completions)
|
||||
.with_ansi_colors(config.use_ansi_coloring)
|
||||
.with_cursor_config(cursor_config);
|
||||
perf(
|
||||
"reedline builder",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
let style_computer = StyleComputer::from_config(engine_state, stack);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
line_editor = if config.use_ansi_coloring {
|
||||
line_editor.with_hinter(Box::new({
|
||||
// As of Nov 2022, "hints" color_config closures only get `null` passed in.
|
||||
let style = style_computer.compute("hints", &Value::nothing(Span::unknown()));
|
||||
CwdAwareHinter::default().with_style(style)
|
||||
}))
|
||||
} else {
|
||||
line_editor.disable_hints()
|
||||
};
|
||||
perf(
|
||||
"reedline coloring/style_computer",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
line_editor = add_menus(line_editor, engine_reference, stack, config).unwrap_or_else(|e| {
|
||||
report_error_new(engine_state, &e);
|
||||
Reedline::create()
|
||||
});
|
||||
perf(
|
||||
"reedline menus",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
let buffer_editor = get_editor(engine_state, stack, Span::unknown());
|
||||
|
||||
line_editor = if let Ok((cmd, args)) = buffer_editor {
|
||||
let mut command = std::process::Command::new(cmd);
|
||||
command
|
||||
.args(args)
|
||||
.envs(env_to_strings(engine_state, stack)?);
|
||||
line_editor.with_buffer_editor(command, temp_file.to_path_buf())
|
||||
} else {
|
||||
line_editor
|
||||
};
|
||||
perf(
|
||||
"reedline buffer_editor",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
if let Some(history) = engine_state.history_config() {
|
||||
start_time = std::time::Instant::now();
|
||||
if history.sync_on_enter {
|
||||
if let Err(e) = line_editor.sync_history() {
|
||||
warn!("Failed to sync history: {}", e);
|
||||
}
|
||||
}
|
||||
perf(
|
||||
"finished repl loop",
|
||||
loop_start_time,
|
||||
"sync_history",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
|
@ -503,7 +396,201 @@ pub fn evaluate_repl(
|
|||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
start_time = std::time::Instant::now();
|
||||
// Changing the line editor based on the found keybindings
|
||||
line_editor = setup_keybindings(engine_state, line_editor);
|
||||
perf(
|
||||
"keybindings",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
// Right before we start our prompt and take input from the user,
|
||||
// fire the "pre_prompt" hook
|
||||
if let Some(hook) = config.hooks.pre_prompt.clone() {
|
||||
if let Err(err) = eval_hook(engine_state, stack, None, vec![], &hook, "pre_prompt") {
|
||||
report_error_new(engine_state, &err);
|
||||
}
|
||||
}
|
||||
perf(
|
||||
"pre-prompt hook",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
// Next, check all the environment variables they ask for
|
||||
// fire the "env_change" hook
|
||||
let config = engine_state.get_config();
|
||||
if let Err(error) =
|
||||
hook::eval_env_change_hook(config.hooks.env_change.clone(), engine_state, stack)
|
||||
{
|
||||
report_error_new(engine_state, &error)
|
||||
}
|
||||
perf(
|
||||
"env-change hook",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
let config = &engine_state.get_config().clone();
|
||||
prompt_update::update_prompt(config, engine_state, stack, nu_prompt);
|
||||
let transient_prompt =
|
||||
prompt_update::make_transient_prompt(config, engine_state, stack, nu_prompt);
|
||||
perf(
|
||||
"update_prompt",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
*entry_num += 1;
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
line_editor = line_editor.with_transient_prompt(transient_prompt);
|
||||
let input = line_editor.read_line(nu_prompt);
|
||||
let shell_integration = config.shell_integration;
|
||||
|
||||
match input {
|
||||
Ok(Signal::Success(s)) => {
|
||||
let hostname = System::host_name();
|
||||
let history_supports_meta = matches!(
|
||||
engine_state.history_config().map(|h| h.file_format),
|
||||
Some(HistoryFileFormat::Sqlite)
|
||||
);
|
||||
|
||||
if history_supports_meta {
|
||||
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`
|
||||
// hook
|
||||
if let Some(hook) = config.hooks.pre_execution.clone() {
|
||||
// Set the REPL buffer to the current command for the "pre_execution" hook
|
||||
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
|
||||
repl.buffer = s.to_string();
|
||||
drop(repl);
|
||||
|
||||
if let Err(err) =
|
||||
eval_hook(engine_state, stack, None, vec![], &hook, "pre_execution")
|
||||
{
|
||||
report_error_new(engine_state, &err);
|
||||
}
|
||||
}
|
||||
|
||||
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
|
||||
repl.cursor_pos = line_editor.current_insertion_point();
|
||||
repl.buffer = line_editor.current_buffer_contents().to_string();
|
||||
drop(repl);
|
||||
|
||||
if shell_integration {
|
||||
run_ansi_sequence(PRE_EXECUTE_MARKER)?;
|
||||
}
|
||||
|
||||
// Actual command execution logic starts from here
|
||||
let start_time = Instant::now();
|
||||
|
||||
match parse_operation(s.clone(), engine_state, stack)? {
|
||||
ReplOperation::AutoCd { cwd, target, span } => {
|
||||
do_auto_cd(target, cwd, stack, engine_state, span);
|
||||
}
|
||||
ReplOperation::RunCommand(cmd) => {
|
||||
line_editor = do_run_cmd(
|
||||
&cmd,
|
||||
stack,
|
||||
engine_state,
|
||||
line_editor,
|
||||
shell_integration,
|
||||
*entry_num,
|
||||
)?;
|
||||
}
|
||||
// as the name implies, we do nothing in this case
|
||||
ReplOperation::DoNothing => {}
|
||||
}
|
||||
|
||||
let cmd_duration = start_time.elapsed();
|
||||
|
||||
stack.add_env_var(
|
||||
"CMD_DURATION_MS".into(),
|
||||
Value::string(format!("{}", cmd_duration.as_millis()), Span::unknown()),
|
||||
);
|
||||
|
||||
if history_supports_meta {
|
||||
fill_in_result_related_history_metadata(
|
||||
&s,
|
||||
engine_state,
|
||||
cmd_duration,
|
||||
stack,
|
||||
&mut line_editor,
|
||||
)?;
|
||||
}
|
||||
|
||||
if shell_integration {
|
||||
do_shell_integration_finalize_command(hostname, engine_state, stack)?;
|
||||
}
|
||||
|
||||
flush_engine_state_repl_buffer(engine_state, &mut line_editor);
|
||||
}
|
||||
Ok(Signal::CtrlC) => {
|
||||
// `Reedline` clears the line content. New prompt is shown
|
||||
if shell_integration {
|
||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
||||
}
|
||||
}
|
||||
Ok(Signal::CtrlD) => {
|
||||
// When exiting clear to a new line
|
||||
if shell_integration {
|
||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
||||
}
|
||||
println!();
|
||||
return Ok((false, line_editor));
|
||||
}
|
||||
Err(err) => {
|
||||
let message = err.to_string();
|
||||
if !message.contains("duration") {
|
||||
eprintln!("Error: {err:?}");
|
||||
// TODO: Identify possible error cases where a hard failure is preferable
|
||||
// Ignoring and reporting could hide bigger problems
|
||||
// e.g. https://github.com/nushell/nushell/issues/6452
|
||||
// Alternatively only allow that expected failures let the REPL loop
|
||||
}
|
||||
if shell_integration {
|
||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
perf(
|
||||
"processing line editor input",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
perf(
|
||||
"finished repl loop",
|
||||
loop_start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
Ok((true, line_editor))
|
||||
}
|
||||
|
||||
///
|
||||
|
|
|
@ -93,6 +93,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
|||
|
||||
// Misc
|
||||
bind_command! {
|
||||
Panic,
|
||||
Source,
|
||||
Tutor,
|
||||
};
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
mod panic;
|
||||
mod source;
|
||||
mod tutor;
|
||||
|
||||
pub use panic::Panic;
|
||||
pub use source::Source;
|
||||
pub use tutor::Tutor;
|
||||
|
|
43
crates/nu-command/src/misc/panic.rs
Normal file
43
crates/nu-command/src/misc/panic.rs
Normal file
|
@ -0,0 +1,43 @@
|
|||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Panic;
|
||||
|
||||
impl Command for Panic {
|
||||
fn name(&self) -> &str {
|
||||
"panic"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Executes a rust panic, useful only for testing."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("panic")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
|
||||
// LsGlobPattern is similar to string, it won't auto-expand
|
||||
// and we use it to track if the user input is quoted.
|
||||
.optional("msg", SyntaxShape::String, "The glob pattern to use.")
|
||||
.category(Category::Experimental)
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let maybe_msg: String = call
|
||||
.opt(engine_state, stack, 0)?
|
||||
.unwrap_or("Panic!".to_string());
|
||||
panic!("{}", maybe_msg)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![]
|
||||
}
|
||||
}
|
|
@ -915,6 +915,20 @@ impl EngineState {
|
|||
pub fn set_startup_time(&mut self, startup_time: i64) {
|
||||
self.startup_time = startup_time;
|
||||
}
|
||||
|
||||
pub fn recover_from_panic(&mut self) {
|
||||
if Mutex::is_poisoned(&self.repl_state) {
|
||||
self.repl_state = Arc::new(Mutex::new(ReplState {
|
||||
buffer: "".to_string(),
|
||||
cursor_pos: 0,
|
||||
}));
|
||||
}
|
||||
if Mutex::is_poisoned(&self.regex_cache) {
|
||||
self.regex_cache = Arc::new(Mutex::new(LruCache::new(
|
||||
NonZeroUsize::new(REGEX_CACHE_SIZE).expect("tried to create cache of size zero"),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EngineState {
|
||||
|
|
|
@ -216,6 +216,7 @@ pub(crate) fn parse_commandline_args(
|
|||
std::process::exit(1);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct NushellCliArgs {
|
||||
pub(crate) redirect_stdin: Option<Spanned<String>>,
|
||||
pub(crate) login_shell: Option<Spanned<String>>,
|
||||
|
|
|
@ -53,6 +53,7 @@ fn get_engine_state() -> EngineState {
|
|||
fn main() -> Result<()> {
|
||||
let entire_start_time = std::time::Instant::now();
|
||||
let mut start_time = std::time::Instant::now();
|
||||
miette::set_panic_hook();
|
||||
let miette_hook = std::panic::take_hook();
|
||||
std::panic::set_hook(Box::new(move |x| {
|
||||
crossterm::terminal::disable_raw_mode().expect("unable to disable raw mode");
|
||||
|
|
Loading…
Reference in a new issue