mirror of
https://github.com/nushell/nushell
synced 2024-11-10 23:24:14 +00:00
Add hooks to cli/repl (#5479)
* Add hooks to cli/repl * Clippy * Clippy
This commit is contained in:
parent
a61d09222f
commit
3a35bf7d4e
6 changed files with 134 additions and 5 deletions
|
@ -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 },
|
||||
)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"),
|
||||
})
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue