Add hooks to cli/repl (#5479)

* Add hooks to cli/repl

* Clippy

* Clippy
This commit is contained in:
JT 2022-05-09 07:28:39 +12:00 committed by GitHub
parent a61d09222f
commit 3a35bf7d4e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 134 additions and 5 deletions

View file

@ -8,7 +8,7 @@ use crate::{
use log::{info, trace};
use miette::{IntoDiagnostic, Result};
use nu_color_config::get_color_config;
use nu_engine::convert_env_values;
use nu_engine::{convert_env_values, eval_block};
use nu_parser::lex;
use nu_protocol::{
engine::{EngineState, Stack, StateWorkingSet},
@ -211,11 +211,29 @@ pub fn evaluate_repl(
);
}
// 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 {
if let Err(err) = run_hook(engine_state, stack, hook) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);
}
}
let input = line_editor.read_line(prompt);
let use_shell_integration = config.shell_integration;
match input {
Ok(Signal::Success(s)) => {
// Right before we start running the code the user gave us,
// fire the "pre_execution" hook
if let Some(hook) = &config.hooks.pre_execution {
if let Err(err) = run_hook(engine_state, stack, hook) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);
}
}
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
@ -359,3 +377,38 @@ pub fn evaluate_repl(
Ok(())
}
pub fn run_hook(
engine_state: &EngineState,
stack: &mut Stack,
value: &Value,
) -> Result<(), ShellError> {
match value {
Value::Block {
val: block_id,
span,
..
} => {
let block = engine_state.get_block(*block_id);
let input = PipelineData::new(*span);
match eval_block(engine_state, stack, block, input, false, false) {
Ok(pipeline_data) => match pipeline_data.into_value(*span) {
Value::Error { error } => Err(error),
_ => Ok(()),
},
Err(err) => Err(err),
}
}
x => match x.span() {
Ok(span) => Err(ShellError::MissingConfigValue(
"block for hook in config".into(),
span,
)),
_ => Err(ShellError::MissingConfigValue(
"block for hook in config".into(),
Span { start: 0, end: 0 },
)),
},
}
}

View file

@ -18,7 +18,7 @@ fn flag_completions() {
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
// Test completions for the 'ls' flags
let suggestions = completer.complete("ls -".into(), 4);
let suggestions = completer.complete("ls -", 4);
assert_eq!(12, suggestions.len());

View file

@ -411,9 +411,9 @@ mod test {
#[test]
fn test_order_paths() {
fn sort<'a>(paths: &'a Vec<&'a str>, abbr: &str) -> Vec<&'a str> {
fn sort<'a>(paths: &'a [&'a str], abbr: &str) -> Vec<&'a str> {
let abbr = Abbr::new_sanitized(abbr);
let mut paths = paths.clone();
let mut paths = paths.to_owned();
paths.sort_by_key(|path| abbr.compare(path).unwrap());
paths

View file

@ -966,7 +966,7 @@ mod test {
.and_then(|p| match p.components().next().unwrap() {
Component::Prefix(prefix_component) => {
let path = Path::new(prefix_component.as_os_str()).join("*");
Some(path.to_path_buf())
Some(path)
}
_ => panic!("no prefix in this path"),
})

View file

@ -24,6 +24,28 @@ pub struct ParsedMenu {
pub source: Value,
}
/// Definition of a parsed menu from the config object
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Hooks {
pub pre_prompt: Option<Value>,
pub pre_execution: Option<Value>,
}
impl Hooks {
pub fn new() -> Self {
Self {
pre_prompt: None,
pre_execution: None,
}
}
}
impl Default for Hooks {
fn default() -> Self {
Self::new()
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Config {
pub filesize_metric: bool,
@ -45,6 +67,7 @@ pub struct Config {
pub log_level: String,
pub keybindings: Vec<ParsedKeybinding>,
pub menus: Vec<ParsedMenu>,
pub hooks: Hooks,
pub rm_always_trash: bool,
pub shell_integration: bool,
pub buffer_editor: String,
@ -74,6 +97,7 @@ impl Default for Config {
log_level: String::new(),
keybindings: Vec::new(),
menus: Vec::new(),
hooks: Hooks::new(),
rm_always_trash: false,
shell_integration: false,
buffer_editor: String::new(),
@ -253,6 +277,13 @@ impl Value {
eprintln!("{:?}", e);
}
},
"hooks" => match create_hooks(value) {
Ok(hooks) => config.hooks = hooks,
Err(e) => {
eprintln!("$config.hooks is not a valid hooks list");
eprintln!("{:?}", e);
}
},
"shell_integration" => {
if let Ok(b) = value.as_bool() {
config.shell_integration = b;
@ -343,6 +374,43 @@ pub fn color_value_string(
}
}
// Parse the hooks to find the blocks to run when the hooks fire
fn create_hooks(value: &Value) -> Result<Hooks, ShellError> {
match value {
Value::Record { cols, vals, span } => {
let mut hooks = Hooks::new();
for idx in 0..cols.len() {
match cols[idx].as_str() {
"pre_prompt" => hooks.pre_prompt = Some(vals[idx].clone()),
"pre_execution" => hooks.pre_execution = Some(vals[idx].clone()),
x => {
return Err(ShellError::UnsupportedConfigValue(
"'pre_prompt' or 'pre_execution'".to_string(),
x.to_string(),
*span,
));
}
}
}
Ok(hooks)
}
v => match v.span() {
Ok(span) => Err(ShellError::UnsupportedConfigValue(
"record for 'hooks' config".into(),
"non-record value".into(),
span,
)),
_ => Err(ShellError::UnsupportedConfigValue(
"record for 'hooks' config".into(),
"non-record value".into(),
Span { start: 0, end: 0 },
)),
},
}
}
// Parses the config object to extract the strings that will compose a keybinding for reedline
fn create_keybindings(value: &Value, config: &Config) -> Result<Vec<ParsedKeybinding>, ShellError> {
match value {

View file

@ -199,6 +199,14 @@ let-env config = {
shell_integration: true # enables terminal markers and a workaround to arrow keys stop working issue
disable_table_indexes: false # set to true to remove the index column from tables
cd_with_abbreviations: false # set to true to allow you to do things like cd s/o/f and nushell expand it to cd some/other/folder
hooks: {
pre_prompt: {
$nothing # replace with source code to run before the prompt is shown
}
pre_execution: {
$nothing # replace with source code to run before the repl input is run
}
}
menus: [
# Configuration for default nushell menus
# Note the lack of souce parameter