mirror of
https://github.com/nushell/nushell.git
synced 2024-11-22 16:33:37 +01:00
Add hooks to cli/repl (#5479)
* Add hooks to cli/repl * Clippy * Clippy
This commit is contained in:
parent
a61d09222f
commit
3a35bf7d4e
@ -8,7 +8,7 @@ use crate::{
|
|||||||
use log::{info, trace};
|
use log::{info, trace};
|
||||||
use miette::{IntoDiagnostic, Result};
|
use miette::{IntoDiagnostic, Result};
|
||||||
use nu_color_config::get_color_config;
|
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_parser::lex;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
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 input = line_editor.read_line(prompt);
|
||||||
let use_shell_integration = config.shell_integration;
|
let use_shell_integration = config.shell_integration;
|
||||||
|
|
||||||
match input {
|
match input {
|
||||||
Ok(Signal::Success(s)) => {
|
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 start_time = Instant::now();
|
||||||
let tokens = lex(s.as_bytes(), 0, &[], &[], false);
|
let tokens = lex(s.as_bytes(), 0, &[], &[], false);
|
||||||
// Check if this is a single call to a directory, if so auto-cd
|
// Check if this is a single call to a directory, if so auto-cd
|
||||||
@ -359,3 +377,38 @@ pub fn evaluate_repl(
|
|||||||
|
|
||||||
Ok(())
|
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);
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
// Test completions for the 'ls' flags
|
// Test completions for the 'ls' flags
|
||||||
let suggestions = completer.complete("ls -".into(), 4);
|
let suggestions = completer.complete("ls -", 4);
|
||||||
|
|
||||||
assert_eq!(12, suggestions.len());
|
assert_eq!(12, suggestions.len());
|
||||||
|
|
||||||
|
@ -411,9 +411,9 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_order_paths() {
|
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 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.sort_by_key(|path| abbr.compare(path).unwrap());
|
||||||
|
|
||||||
paths
|
paths
|
||||||
|
@ -966,7 +966,7 @@ mod test {
|
|||||||
.and_then(|p| match p.components().next().unwrap() {
|
.and_then(|p| match p.components().next().unwrap() {
|
||||||
Component::Prefix(prefix_component) => {
|
Component::Prefix(prefix_component) => {
|
||||||
let path = Path::new(prefix_component.as_os_str()).join("*");
|
let path = Path::new(prefix_component.as_os_str()).join("*");
|
||||||
Some(path.to_path_buf())
|
Some(path)
|
||||||
}
|
}
|
||||||
_ => panic!("no prefix in this path"),
|
_ => panic!("no prefix in this path"),
|
||||||
})
|
})
|
||||||
|
@ -24,6 +24,28 @@ pub struct ParsedMenu {
|
|||||||
pub source: Value,
|
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)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub filesize_metric: bool,
|
pub filesize_metric: bool,
|
||||||
@ -45,6 +67,7 @@ pub struct Config {
|
|||||||
pub log_level: String,
|
pub log_level: String,
|
||||||
pub keybindings: Vec<ParsedKeybinding>,
|
pub keybindings: Vec<ParsedKeybinding>,
|
||||||
pub menus: Vec<ParsedMenu>,
|
pub menus: Vec<ParsedMenu>,
|
||||||
|
pub hooks: Hooks,
|
||||||
pub rm_always_trash: bool,
|
pub rm_always_trash: bool,
|
||||||
pub shell_integration: bool,
|
pub shell_integration: bool,
|
||||||
pub buffer_editor: String,
|
pub buffer_editor: String,
|
||||||
@ -74,6 +97,7 @@ impl Default for Config {
|
|||||||
log_level: String::new(),
|
log_level: String::new(),
|
||||||
keybindings: Vec::new(),
|
keybindings: Vec::new(),
|
||||||
menus: Vec::new(),
|
menus: Vec::new(),
|
||||||
|
hooks: Hooks::new(),
|
||||||
rm_always_trash: false,
|
rm_always_trash: false,
|
||||||
shell_integration: false,
|
shell_integration: false,
|
||||||
buffer_editor: String::new(),
|
buffer_editor: String::new(),
|
||||||
@ -253,6 +277,13 @@ impl Value {
|
|||||||
eprintln!("{:?}", e);
|
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" => {
|
"shell_integration" => {
|
||||||
if let Ok(b) = value.as_bool() {
|
if let Ok(b) = value.as_bool() {
|
||||||
config.shell_integration = b;
|
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
|
// 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> {
|
fn create_keybindings(value: &Value, config: &Config) -> Result<Vec<ParsedKeybinding>, ShellError> {
|
||||||
match value {
|
match value {
|
||||||
|
@ -199,6 +199,14 @@ let-env config = {
|
|||||||
shell_integration: true # enables terminal markers and a workaround to arrow keys stop working issue
|
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
|
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
|
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: [
|
menus: [
|
||||||
# Configuration for default nushell menus
|
# Configuration for default nushell menus
|
||||||
# Note the lack of souce parameter
|
# Note the lack of souce parameter
|
||||||
|
Loading…
Reference in New Issue
Block a user