mirror of
https://github.com/nushell/nushell.git
synced 2025-01-11 08:48:23 +01:00
Tidy up the REPL main loop (#11655)
While working on #11288 I was having some trouble tracking the main REPL loop, so I've sent in a bunch of tiny refactorings on this branch. These are almost all of the "move code from one place to another" variety, and each commit is meant to be independent, _except for the last one_, which is trying to be a bit more clever to handle the decision of autocd'ing vs running a command. Feel free to just go through each commit and cherry pick the ones that look good. This leads to `evaluate_repl` going from ending on line 715 to ending on line 395. Again, this is mostly just moving code around, but I think this set of changes will make other changes around juggling the stack to avoid cloning easier to review.
This commit is contained in:
parent
d9b324c5d3
commit
d40019a141
@ -17,8 +17,8 @@ use nu_protocol::{
|
||||
config::NuCursorShape,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
eval_const::create_nu_constant,
|
||||
report_error, report_error_new, HistoryConfig, HistoryFileFormat, PipelineData, ShellError,
|
||||
Span, Spanned, Value, NU_VARIABLE_ID,
|
||||
report_error_new, HistoryConfig, HistoryFileFormat, PipelineData, ShellError, Span, Spanned,
|
||||
Value, NU_VARIABLE_ID,
|
||||
};
|
||||
use nu_utils::utils::perf;
|
||||
use reedline::{
|
||||
@ -30,7 +30,7 @@ use std::{
|
||||
io::{self, IsTerminal, Write},
|
||||
path::PathBuf,
|
||||
sync::atomic::Ordering,
|
||||
time::Instant,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use sysinfo::System;
|
||||
|
||||
@ -44,6 +44,9 @@ const PRE_EXECUTE_MARKER: &str = "\x1b]133;C\x1b\\";
|
||||
// const CMD_FINISHED_MARKER: &str = "\x1b]133;D;{}\x1b\\";
|
||||
const RESET_APPLICATION_MODE: &str = "\x1b[?1l";
|
||||
|
||||
///
|
||||
/// The main REPL loop, including spinning up the prompt itself.
|
||||
///
|
||||
pub fn evaluate_repl(
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
@ -57,15 +60,7 @@ pub fn evaluate_repl(
|
||||
let config = engine_state.get_config();
|
||||
let use_color = config.use_ansi_coloring;
|
||||
|
||||
// Guard against invocation without a connected terminal.
|
||||
// reedline / crossterm event polling will fail without a connected tty
|
||||
if !std::io::stdin().is_terminal() {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
"Nushell launched as a REPL, but STDIN is not a TTY; either launch in a valid terminal or provide arguments to invoke a script!",
|
||||
))
|
||||
.into_diagnostic();
|
||||
}
|
||||
confirm_stdin_is_terminal()?;
|
||||
|
||||
let mut entry_num = 0;
|
||||
|
||||
@ -74,8 +69,7 @@ pub fn evaluate_repl(
|
||||
let start_time = std::time::Instant::now();
|
||||
// Translate environment variables from Strings to Values
|
||||
if let Some(e) = convert_env_values(engine_state, stack) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
report_error_new(engine_state, &e);
|
||||
}
|
||||
perf(
|
||||
"translate env vars",
|
||||
@ -112,23 +106,7 @@ pub fn evaluate_repl(
|
||||
if let Some(history) = engine_state.history_config() {
|
||||
start_time = std::time::Instant::now();
|
||||
|
||||
// Setup history_isolation aka "history per session"
|
||||
let history_session_id = if history.isolation {
|
||||
Reedline::create_history_session_id()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(path) = crate::config_files::get_history_path(nushell_path, history.file_format)
|
||||
{
|
||||
line_editor = update_line_editor_history(
|
||||
engine_state,
|
||||
path,
|
||||
history,
|
||||
line_editor,
|
||||
history_session_id,
|
||||
)?
|
||||
};
|
||||
line_editor = setup_history(nushell_path, engine_state, line_editor, history)?;
|
||||
|
||||
perf(
|
||||
"setup history",
|
||||
@ -169,9 +147,7 @@ pub fn evaluate_repl(
|
||||
);
|
||||
}
|
||||
|
||||
if engine_state.get_config().use_kitty_protocol && !reedline::kitty_protocol_available() {
|
||||
warn!("Terminal doesn't support use_kitty_protocol config");
|
||||
}
|
||||
kitty_protocol_healthcheck(engine_state);
|
||||
|
||||
loop {
|
||||
let loop_start_time = std::time::Instant::now();
|
||||
@ -282,8 +258,7 @@ pub fn evaluate_repl(
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
line_editor = add_menus(line_editor, engine_reference, stack, config).unwrap_or_else(|e| {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
report_error_new(engine_state, &e);
|
||||
Reedline::create()
|
||||
});
|
||||
perf(
|
||||
@ -335,26 +310,7 @@ pub fn evaluate_repl(
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
// Changing the line editor based on the found keybindings
|
||||
line_editor = match create_keybindings(config) {
|
||||
Ok(keybindings) => match keybindings {
|
||||
KeybindingsMode::Emacs(keybindings) => {
|
||||
let edit_mode = Box::new(Emacs::new(keybindings));
|
||||
line_editor.with_edit_mode(edit_mode)
|
||||
}
|
||||
KeybindingsMode::Vi {
|
||||
insert_keybindings,
|
||||
normal_keybindings,
|
||||
} => {
|
||||
let edit_mode = Box::new(Vi::new(insert_keybindings, normal_keybindings));
|
||||
line_editor.with_edit_mode(edit_mode)
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
line_editor
|
||||
}
|
||||
};
|
||||
line_editor = setup_keybindings(engine_state, line_editor);
|
||||
perf(
|
||||
"keybindings",
|
||||
start_time,
|
||||
@ -427,17 +383,9 @@ pub fn evaluate_repl(
|
||||
engine_state.history_config().map(|h| h.file_format),
|
||||
Some(HistoryFileFormat::Sqlite)
|
||||
);
|
||||
if history_supports_meta && !s.is_empty() && line_editor.has_last_command_context()
|
||||
{
|
||||
line_editor
|
||||
.update_last_command_context(&|mut c| {
|
||||
c.start_timestamp = Some(chrono::Utc::now());
|
||||
c.hostname = hostname.clone();
|
||||
|
||||
c.cwd = Some(StateWorkingSet::new(engine_state).get_cwd());
|
||||
c
|
||||
})
|
||||
.into_diagnostic()?; // todo: don't stop repl if error here?
|
||||
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`
|
||||
@ -464,127 +412,27 @@ pub fn evaluate_repl(
|
||||
run_ansi_sequence(PRE_EXECUTE_MARKER)?;
|
||||
}
|
||||
|
||||
// Actual command execution logic starts from here
|
||||
let start_time = Instant::now();
|
||||
let tokens = lex(s.as_bytes(), 0, &[], &[], false);
|
||||
// Check if this is a single call to a directory, if so auto-cd
|
||||
let cwd = nu_engine::env::current_dir_str(engine_state, stack)?;
|
||||
|
||||
let mut orig = s.clone();
|
||||
if orig.starts_with('`') {
|
||||
orig = trim_quotes_str(&orig).to_string()
|
||||
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 path = nu_path::expand_path_with(&orig, &cwd);
|
||||
|
||||
if looks_like_path(&orig) && path.is_dir() && tokens.0.len() == 1 {
|
||||
// We have an auto-cd
|
||||
let (path, span) = {
|
||||
if !path.exists() {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(
|
||||
&working_set,
|
||||
&ShellError::DirectoryNotFound {
|
||||
dir: path.to_string_lossy().to_string(),
|
||||
span: tokens.0[0].span,
|
||||
},
|
||||
);
|
||||
}
|
||||
let path = nu_path::canonicalize_with(path, &cwd)
|
||||
.expect("internal error: cannot canonicalize known path");
|
||||
(path.to_string_lossy().to_string(), tokens.0[0].span)
|
||||
};
|
||||
|
||||
stack.add_env_var("OLDPWD".into(), Value::string(cwd.clone(), Span::unknown()));
|
||||
|
||||
//FIXME: this only changes the current scope, but instead this environment variable
|
||||
//should probably be a block that loads the information from the state in the overlay
|
||||
stack.add_env_var("PWD".into(), Value::string(path.clone(), Span::unknown()));
|
||||
let cwd = Value::string(cwd, span);
|
||||
|
||||
let shells = stack.get_env_var(engine_state, "NUSHELL_SHELLS");
|
||||
let mut shells = if let Some(v) = shells {
|
||||
v.as_list()
|
||||
.map(|x| x.to_vec())
|
||||
.unwrap_or_else(|_| vec![cwd])
|
||||
} else {
|
||||
vec![cwd]
|
||||
};
|
||||
|
||||
let current_shell = stack.get_env_var(engine_state, "NUSHELL_CURRENT_SHELL");
|
||||
let current_shell = if let Some(v) = current_shell {
|
||||
v.as_int().unwrap_or_default() as usize
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let last_shell = stack.get_env_var(engine_state, "NUSHELL_LAST_SHELL");
|
||||
let last_shell = if let Some(v) = last_shell {
|
||||
v.as_int().unwrap_or_default() as usize
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
shells[current_shell] = Value::string(path, span);
|
||||
|
||||
stack.add_env_var("NUSHELL_SHELLS".into(), Value::list(shells, span));
|
||||
stack.add_env_var(
|
||||
"NUSHELL_LAST_SHELL".into(),
|
||||
Value::int(last_shell as i64, span),
|
||||
);
|
||||
} else if !s.trim().is_empty() {
|
||||
trace!("eval source: {}", s);
|
||||
|
||||
let mut cmds = s.split_whitespace();
|
||||
if let Some("exit") = cmds.next() {
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
let _ = parse(&mut working_set, None, s.as_bytes(), false);
|
||||
|
||||
if working_set.parse_errors.is_empty() {
|
||||
match cmds.next() {
|
||||
Some(s) => {
|
||||
if let Ok(n) = s.parse::<i32>() {
|
||||
drop(line_editor);
|
||||
std::process::exit(n);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
drop(line_editor);
|
||||
std::process::exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if shell_integration {
|
||||
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
||||
let path = cwd.as_string()?;
|
||||
|
||||
// Try to abbreviate string for windows title
|
||||
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
|
||||
path.replace(&p.as_path().display().to_string(), "~")
|
||||
} else {
|
||||
path
|
||||
};
|
||||
let binary_name = s.split_whitespace().next();
|
||||
|
||||
if let Some(binary_name) = binary_name {
|
||||
run_ansi_sequence(&format!(
|
||||
"\x1b]2;{maybe_abbrev_path}> {binary_name}\x07"
|
||||
))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eval_source(
|
||||
engine_state,
|
||||
stack,
|
||||
s.as_bytes(),
|
||||
&format!("entry #{entry_num}"),
|
||||
PipelineData::empty(),
|
||||
false,
|
||||
);
|
||||
}
|
||||
let cmd_duration = start_time.elapsed();
|
||||
|
||||
stack.add_env_var(
|
||||
@ -592,77 +440,21 @@ pub fn evaluate_repl(
|
||||
Value::string(format!("{}", cmd_duration.as_millis()), Span::unknown()),
|
||||
);
|
||||
|
||||
if history_supports_meta && !s.is_empty() && line_editor.has_last_command_context()
|
||||
{
|
||||
line_editor
|
||||
.update_last_command_context(&|mut c| {
|
||||
c.duration = Some(cmd_duration);
|
||||
c.exit_status = stack
|
||||
.get_env_var(engine_state, "LAST_EXIT_CODE")
|
||||
.and_then(|e| e.as_i64().ok());
|
||||
c
|
||||
})
|
||||
.into_diagnostic()?; // todo: don't stop repl if error here?
|
||||
if history_supports_meta {
|
||||
fill_in_result_related_history_metadata(
|
||||
&s,
|
||||
engine_state,
|
||||
cmd_duration,
|
||||
stack,
|
||||
&mut line_editor,
|
||||
)?;
|
||||
}
|
||||
|
||||
if shell_integration {
|
||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
||||
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
||||
let path = cwd.as_string()?;
|
||||
|
||||
// Supported escape sequences of Microsoft's Visual Studio Code (vscode)
|
||||
// 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 we're in vscode, run their specific ansi escape sequence.
|
||||
// This is helpful for ctrl+g to change directories in the terminal.
|
||||
run_ansi_sequence(&format!("\x1b]633;P;Cwd={}\x1b\\", path))?;
|
||||
} else {
|
||||
// Otherwise, communicate the path as OSC 7 (often used for spawning new tabs in the same dir)
|
||||
run_ansi_sequence(&format!(
|
||||
"\x1b]7;file://{}{}{}\x1b\\",
|
||||
percent_encoding::utf8_percent_encode(
|
||||
&hostname.unwrap_or_else(|| "localhost".to_string()),
|
||||
percent_encoding::CONTROLS
|
||||
),
|
||||
if path.starts_with('/') { "" } else { "/" },
|
||||
percent_encoding::utf8_percent_encode(
|
||||
&path,
|
||||
percent_encoding::CONTROLS
|
||||
)
|
||||
))?;
|
||||
}
|
||||
|
||||
// Try to abbreviate string for windows title
|
||||
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
|
||||
path.replace(&p.as_path().display().to_string(), "~")
|
||||
} else {
|
||||
path
|
||||
};
|
||||
|
||||
// Set window title too
|
||||
// https://tldp.org/HOWTO/Xterm-Title-3.html
|
||||
// ESC]0;stringBEL -- Set icon name and window title to string
|
||||
// ESC]1;stringBEL -- Set icon name to string
|
||||
// ESC]2;stringBEL -- Set window title to string
|
||||
run_ansi_sequence(&format!("\x1b]2;{maybe_abbrev_path}\x07"))?;
|
||||
}
|
||||
run_ansi_sequence(RESET_APPLICATION_MODE)?;
|
||||
do_shell_integration_finalize_command(hostname, engine_state, stack)?;
|
||||
}
|
||||
|
||||
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
|
||||
line_editor.run_edit_commands(&[
|
||||
EditCommand::Clear,
|
||||
EditCommand::InsertString(repl.buffer.to_string()),
|
||||
EditCommand::MoveToPosition {
|
||||
position: repl.cursor_pos,
|
||||
select: false,
|
||||
},
|
||||
]);
|
||||
repl.buffer = "".to_string();
|
||||
repl.cursor_pos = 0;
|
||||
drop(repl);
|
||||
flush_engine_state_repl_buffer(engine_state, &mut line_editor);
|
||||
}
|
||||
Ok(Signal::CtrlC) => {
|
||||
// `Reedline` clears the line content. New prompt is shown
|
||||
@ -714,6 +506,364 @@ pub fn evaluate_repl(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Put in history metadata not related to the result of running the command
|
||||
///
|
||||
fn prepare_history_metadata(
|
||||
s: &str,
|
||||
hostname: &Option<String>,
|
||||
engine_state: &EngineState,
|
||||
line_editor: &mut Reedline,
|
||||
) -> Result<()> {
|
||||
if !s.is_empty() && line_editor.has_last_command_context() {
|
||||
line_editor
|
||||
.update_last_command_context(&|mut c| {
|
||||
c.start_timestamp = Some(chrono::Utc::now());
|
||||
c.hostname = hostname.clone();
|
||||
|
||||
c.cwd = Some(StateWorkingSet::new(engine_state).get_cwd());
|
||||
c
|
||||
})
|
||||
.into_diagnostic()?; // todo: don't stop repl if error here?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Fills in history item metadata based on the execution result (notably duration and exit code)
|
||||
///
|
||||
fn fill_in_result_related_history_metadata(
|
||||
s: &str,
|
||||
engine_state: &EngineState,
|
||||
cmd_duration: Duration,
|
||||
stack: &mut Stack,
|
||||
line_editor: &mut Reedline,
|
||||
) -> Result<()> {
|
||||
if !s.is_empty() && line_editor.has_last_command_context() {
|
||||
line_editor
|
||||
.update_last_command_context(&|mut c| {
|
||||
c.duration = Some(cmd_duration);
|
||||
c.exit_status = stack
|
||||
.get_env_var(engine_state, "LAST_EXIT_CODE")
|
||||
.and_then(|e| e.as_i64().ok());
|
||||
c
|
||||
})
|
||||
.into_diagnostic()?; // todo: don't stop repl if error here?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The kinds of operations you can do in a single loop iteration of the REPL
|
||||
enum ReplOperation {
|
||||
/// "auto-cd": change directory by typing it in directly
|
||||
AutoCd {
|
||||
/// the current working directory
|
||||
cwd: String,
|
||||
/// the target
|
||||
target: PathBuf,
|
||||
/// span information for debugging
|
||||
span: Span,
|
||||
},
|
||||
/// run a command
|
||||
RunCommand(String),
|
||||
/// do nothing (usually through an empty string)
|
||||
DoNothing,
|
||||
}
|
||||
|
||||
///
|
||||
/// Parses one "REPL line" of input, to try and derive intent.
|
||||
/// Notably, this is where we detect whether the user is attempting an
|
||||
/// "auto-cd" (writing a relative path directly instead of `cd path`)
|
||||
///
|
||||
/// Returns the ReplOperation we believe the user wants to do
|
||||
///
|
||||
fn parse_operation(
|
||||
s: String,
|
||||
engine_state: &EngineState,
|
||||
stack: &Stack,
|
||||
) -> Result<ReplOperation, ErrReport> {
|
||||
let tokens = lex(s.as_bytes(), 0, &[], &[], false);
|
||||
// Check if this is a single call to a directory, if so auto-cd
|
||||
let cwd = nu_engine::env::current_dir_str(engine_state, stack)?;
|
||||
let mut orig = s.clone();
|
||||
if orig.starts_with('`') {
|
||||
orig = trim_quotes_str(&orig).to_string()
|
||||
}
|
||||
|
||||
let path = nu_path::expand_path_with(&orig, &cwd);
|
||||
if looks_like_path(&orig) && path.is_dir() && tokens.0.len() == 1 {
|
||||
Ok(ReplOperation::AutoCd {
|
||||
cwd,
|
||||
target: path,
|
||||
span: tokens.0[0].span,
|
||||
})
|
||||
} else if !s.trim().is_empty() {
|
||||
Ok(ReplOperation::RunCommand(s))
|
||||
} else {
|
||||
Ok(ReplOperation::DoNothing)
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Execute an "auto-cd" operation, changing the current working directory.
|
||||
///
|
||||
fn do_auto_cd(
|
||||
path: PathBuf,
|
||||
cwd: String,
|
||||
stack: &mut Stack,
|
||||
engine_state: &mut EngineState,
|
||||
span: Span,
|
||||
) {
|
||||
let path = {
|
||||
if !path.exists() {
|
||||
report_error_new(
|
||||
engine_state,
|
||||
&ShellError::DirectoryNotFound {
|
||||
dir: path.to_string_lossy().to_string(),
|
||||
span,
|
||||
},
|
||||
);
|
||||
}
|
||||
let path = nu_path::canonicalize_with(path, &cwd)
|
||||
.expect("internal error: cannot canonicalize known path");
|
||||
path.to_string_lossy().to_string()
|
||||
};
|
||||
|
||||
stack.add_env_var("OLDPWD".into(), Value::string(cwd.clone(), Span::unknown()));
|
||||
|
||||
//FIXME: this only changes the current scope, but instead this environment variable
|
||||
//should probably be a block that loads the information from the state in the overlay
|
||||
stack.add_env_var("PWD".into(), Value::string(path.clone(), Span::unknown()));
|
||||
let cwd = Value::string(cwd, span);
|
||||
|
||||
let shells = stack.get_env_var(engine_state, "NUSHELL_SHELLS");
|
||||
let mut shells = if let Some(v) = shells {
|
||||
v.as_list()
|
||||
.map(|x| x.to_vec())
|
||||
.unwrap_or_else(|_| vec![cwd])
|
||||
} else {
|
||||
vec![cwd]
|
||||
};
|
||||
|
||||
let current_shell = stack.get_env_var(engine_state, "NUSHELL_CURRENT_SHELL");
|
||||
let current_shell = if let Some(v) = current_shell {
|
||||
v.as_int().unwrap_or_default() as usize
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let last_shell = stack.get_env_var(engine_state, "NUSHELL_LAST_SHELL");
|
||||
let last_shell = if let Some(v) = last_shell {
|
||||
v.as_int().unwrap_or_default() as usize
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
shells[current_shell] = Value::string(path, span);
|
||||
|
||||
stack.add_env_var("NUSHELL_SHELLS".into(), Value::list(shells, span));
|
||||
stack.add_env_var(
|
||||
"NUSHELL_LAST_SHELL".into(),
|
||||
Value::int(last_shell as i64, span),
|
||||
);
|
||||
}
|
||||
|
||||
///
|
||||
/// Run a command as received from reedline. This is where we are actually
|
||||
/// running a thing!
|
||||
///
|
||||
fn do_run_cmd(
|
||||
s: &str,
|
||||
stack: &mut Stack,
|
||||
engine_state: &mut EngineState,
|
||||
// we pass in the line editor so it can be dropped in the case of a process exit
|
||||
// (in the normal case we don't want to drop it so return it as-is otherwise)
|
||||
line_editor: Reedline,
|
||||
shell_integration: bool,
|
||||
entry_num: usize,
|
||||
) -> Result<Reedline> {
|
||||
trace!("eval source: {}", s);
|
||||
|
||||
let mut cmds = s.split_whitespace();
|
||||
if let Some("exit") = cmds.next() {
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
let _ = parse(&mut working_set, None, s.as_bytes(), false);
|
||||
|
||||
if working_set.parse_errors.is_empty() {
|
||||
match cmds.next() {
|
||||
Some(s) => {
|
||||
if let Ok(n) = s.parse::<i32>() {
|
||||
drop(line_editor);
|
||||
std::process::exit(n);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
drop(line_editor);
|
||||
std::process::exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if shell_integration {
|
||||
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
||||
let path = cwd.as_string()?;
|
||||
|
||||
// Try to abbreviate string for windows title
|
||||
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
|
||||
path.replace(&p.as_path().display().to_string(), "~")
|
||||
} else {
|
||||
path
|
||||
};
|
||||
let binary_name = s.split_whitespace().next();
|
||||
|
||||
if let Some(binary_name) = binary_name {
|
||||
run_ansi_sequence(&format!("\x1b]2;{maybe_abbrev_path}> {binary_name}\x07"))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eval_source(
|
||||
engine_state,
|
||||
stack,
|
||||
s.as_bytes(),
|
||||
&format!("entry #{entry_num}"),
|
||||
PipelineData::empty(),
|
||||
false,
|
||||
);
|
||||
|
||||
Ok(line_editor)
|
||||
}
|
||||
|
||||
///
|
||||
/// Output some things and set environment variables so shells with the right integration
|
||||
/// can have more information about what is going on (after we have run a command)
|
||||
///
|
||||
fn do_shell_integration_finalize_command(
|
||||
hostname: Option<String>,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
) -> Result<()> {
|
||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
||||
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
||||
let path = cwd.as_string()?;
|
||||
|
||||
// Supported escape sequences of Microsoft's Visual Studio Code (vscode)
|
||||
// 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 we're in vscode, run their specific ansi escape sequence.
|
||||
// This is helpful for ctrl+g to change directories in the terminal.
|
||||
run_ansi_sequence(&format!("\x1b]633;P;Cwd={}\x1b\\", path))?;
|
||||
} else {
|
||||
// Otherwise, communicate the path as OSC 7 (often used for spawning new tabs in the same dir)
|
||||
run_ansi_sequence(&format!(
|
||||
"\x1b]7;file://{}{}{}\x1b\\",
|
||||
percent_encoding::utf8_percent_encode(
|
||||
&hostname.unwrap_or_else(|| "localhost".to_string()),
|
||||
percent_encoding::CONTROLS
|
||||
),
|
||||
if path.starts_with('/') { "" } else { "/" },
|
||||
percent_encoding::utf8_percent_encode(&path, percent_encoding::CONTROLS)
|
||||
))?;
|
||||
}
|
||||
|
||||
// Try to abbreviate string for windows title
|
||||
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
|
||||
path.replace(&p.as_path().display().to_string(), "~")
|
||||
} else {
|
||||
path
|
||||
};
|
||||
|
||||
// Set window title too
|
||||
// https://tldp.org/HOWTO/Xterm-Title-3.html
|
||||
// ESC]0;stringBEL -- Set icon name and window title to string
|
||||
// ESC]1;stringBEL -- Set icon name to string
|
||||
// ESC]2;stringBEL -- Set window title to string
|
||||
run_ansi_sequence(&format!("\x1b]2;{maybe_abbrev_path}\x07"))?;
|
||||
}
|
||||
run_ansi_sequence(RESET_APPLICATION_MODE)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Clear the screen and output anything remaining in the EngineState buffer.
|
||||
///
|
||||
fn flush_engine_state_repl_buffer(engine_state: &mut EngineState, line_editor: &mut Reedline) {
|
||||
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
|
||||
line_editor.run_edit_commands(&[
|
||||
EditCommand::Clear,
|
||||
EditCommand::InsertString(repl.buffer.to_string()),
|
||||
EditCommand::MoveToPosition {
|
||||
position: repl.cursor_pos,
|
||||
select: false,
|
||||
},
|
||||
]);
|
||||
repl.buffer = "".to_string();
|
||||
repl.cursor_pos = 0;
|
||||
}
|
||||
|
||||
///
|
||||
/// Setup history management for Reedline
|
||||
///
|
||||
fn setup_history(
|
||||
nushell_path: &str,
|
||||
engine_state: &mut EngineState,
|
||||
line_editor: Reedline,
|
||||
history: HistoryConfig,
|
||||
) -> Result<Reedline> {
|
||||
// Setup history_isolation aka "history per session"
|
||||
let history_session_id = if history.isolation {
|
||||
Reedline::create_history_session_id()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(path) = crate::config_files::get_history_path(nushell_path, history.file_format) {
|
||||
return update_line_editor_history(
|
||||
engine_state,
|
||||
path,
|
||||
history,
|
||||
line_editor,
|
||||
history_session_id,
|
||||
);
|
||||
};
|
||||
Ok(line_editor)
|
||||
}
|
||||
|
||||
///
|
||||
/// Setup Reedline keybindingds based on the provided config
|
||||
///
|
||||
fn setup_keybindings(engine_state: &EngineState, line_editor: Reedline) -> Reedline {
|
||||
return match create_keybindings(engine_state.get_config()) {
|
||||
Ok(keybindings) => match keybindings {
|
||||
KeybindingsMode::Emacs(keybindings) => {
|
||||
let edit_mode = Box::new(Emacs::new(keybindings));
|
||||
line_editor.with_edit_mode(edit_mode)
|
||||
}
|
||||
KeybindingsMode::Vi {
|
||||
insert_keybindings,
|
||||
normal_keybindings,
|
||||
} => {
|
||||
let edit_mode = Box::new(Vi::new(insert_keybindings, normal_keybindings));
|
||||
line_editor.with_edit_mode(edit_mode)
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
report_error_new(engine_state, &e);
|
||||
line_editor
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
///
|
||||
/// Make sure that the terminal supports the kitty protocol if the config is asking for it
|
||||
///
|
||||
fn kitty_protocol_healthcheck(engine_state: &EngineState) {
|
||||
if engine_state.get_config().use_kitty_protocol && !reedline::kitty_protocol_available() {
|
||||
warn!("Terminal doesn't support use_kitty_protocol config");
|
||||
}
|
||||
}
|
||||
|
||||
fn store_history_id_in_engine(engine_state: &mut EngineState, line_editor: &Reedline) {
|
||||
let session_id = line_editor
|
||||
.get_history_session_id()
|
||||
@ -754,6 +904,18 @@ fn update_line_editor_history(
|
||||
Ok(line_editor)
|
||||
}
|
||||
|
||||
fn confirm_stdin_is_terminal() -> Result<()> {
|
||||
// Guard against invocation without a connected terminal.
|
||||
// reedline / crossterm event polling will fail without a connected tty
|
||||
if !std::io::stdin().is_terminal() {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
"Nushell launched as a REPL, but STDIN is not a TTY; either launch in a valid terminal or provide arguments to invoke a script!",
|
||||
))
|
||||
.into_diagnostic();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn map_nucursorshape_to_cursorshape(shape: NuCursorShape) -> Option<SetCursorStyle> {
|
||||
match shape {
|
||||
NuCursorShape::Block => Some(SetCursorStyle::SteadyBlock),
|
||||
|
Loading…
Reference in New Issue
Block a user