From c158d29577768f9c98ccb5602beb463be187298f Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 5 Jan 2022 15:35:50 +1100 Subject: [PATCH] Add shells support (#671) --- crates/nu-command/src/default_context.rs | 5 ++ crates/nu-command/src/shells/enter.rs | 95 ++++++++++++++++++++++++ crates/nu-command/src/shells/exit.rs | 81 ++++++++++++++++++-- crates/nu-command/src/shells/g.rs | 78 +++++++++++++++++++ crates/nu-command/src/shells/mod.rs | 10 +++ crates/nu-command/src/shells/n.rs | 79 ++++++++++++++++++++ crates/nu-command/src/shells/p.rs | 79 ++++++++++++++++++++ crates/nu-command/src/shells/shells_.rs | 72 ++++++++++++++++++ src/main.rs | 22 +++--- 9 files changed, 504 insertions(+), 17 deletions(-) create mode 100644 crates/nu-command/src/shells/enter.rs create mode 100644 crates/nu-command/src/shells/g.rs create mode 100644 crates/nu-command/src/shells/n.rs create mode 100644 crates/nu-command/src/shells/p.rs create mode 100644 crates/nu-command/src/shells/shells_.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 8e66a4c60a..70d2b862d8 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -181,7 +181,12 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { // Shells bind_command! { + Enter, Exit, + GotoShell, + NextShell, + PrevShell, + Shells, }; // Formats diff --git a/crates/nu-command/src/shells/enter.rs b/crates/nu-command/src/shells/enter.rs new file mode 100644 index 0000000000..ee6ae7649a --- /dev/null +++ b/crates/nu-command/src/shells/enter.rs @@ -0,0 +1,95 @@ +use nu_engine::{current_dir, CallExt}; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, PipelineData, ShellError, Signature, SyntaxShape, Value}; + +/// Source a file for environment variables. +#[derive(Clone)] +pub struct Enter; + +impl Command for Enter { + fn name(&self) -> &str { + "enter" + } + + fn signature(&self) -> Signature { + Signature::build("enter") + .required( + "path", + SyntaxShape::Filepath, + "the path to enter as a new shell", + ) + .category(Category::Shells) + } + + fn usage(&self) -> &str { + "Enters a new shell at the given path." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let new_path: Value = call.req(engine_state, stack, 0)?; + + let cwd = current_dir(engine_state, stack)?; + + if let Ok(s) = new_path.as_string() { + let path = nu_path::expand_path_with(s, &cwd); + if !path.exists() { + return Err(ShellError::DirectoryNotFound(new_path.span()?)); + } + } + + let cwd = Value::String { + val: cwd.to_string_lossy().to_string(), + span: call.head, + }; + + 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 mut current_shell = if let Some(v) = current_shell { + v.as_integer().unwrap_or_default() as usize + } else { + 0 + }; + + if current_shell + 1 > shells.len() { + shells.push(new_path.clone()); + current_shell = shells.len(); + } else { + shells.insert(current_shell + 1, new_path.clone()); + current_shell += 1; + } + + stack.add_env_var( + "NUSHELL_SHELLS".into(), + Value::List { + vals: shells, + span: call.head, + }, + ); + stack.add_env_var( + "NUSHELL_CURRENT_SHELL".into(), + Value::Int { + val: current_shell as i64, + span: call.head, + }, + ); + + stack.add_env_var("PWD".into(), new_path); + + Ok(PipelineData::new(call.head)) + } +} diff --git a/crates/nu-command/src/shells/exit.rs b/crates/nu-command/src/shells/exit.rs index 092c6ef08b..e2a355922d 100644 --- a/crates/nu-command/src/shells/exit.rs +++ b/crates/nu-command/src/shells/exit.rs @@ -1,6 +1,7 @@ +use nu_engine::{current_dir, CallExt}; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{Category, PipelineData, ShellError, Signature}; +use nu_protocol::{Category, PipelineData, ShellError, Signature, SyntaxShape, Value}; /// Source a file for environment variables. #[derive(Clone)] @@ -12,7 +13,14 @@ impl Command for Exit { } fn signature(&self) -> Signature { - Signature::build("exit").category(Category::Shells) + Signature::build("exit") + .optional( + "exit-code", + SyntaxShape::Int, + "Exit code to return immediately with", + ) + .switch("now", "Exit out of the shell immediately", Some('n')) + .category(Category::Shells) } fn usage(&self) -> &str { @@ -21,13 +29,72 @@ impl Command for Exit { fn run( &self, - _engine_state: &EngineState, - _stack: &mut Stack, - _call: &Call, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, _input: PipelineData, ) -> Result { - //TODO: add more shell support + let exit_code: Option = call.opt(engine_state, stack, 0)?; - std::process::exit(0); + if let Some(exit_code) = exit_code { + std::process::exit(exit_code as i32); + } + + if call.has_flag("now") { + std::process::exit(0); + } + + let cwd = current_dir(engine_state, stack)?; + let cwd = Value::String { + val: cwd.to_string_lossy().to_string(), + span: call.head, + }; + + 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 mut current_shell = if let Some(v) = current_shell { + v.as_integer().unwrap_or_default() as usize + } else { + 0 + }; + + shells.remove(current_shell); + + if current_shell == shells.len() && !shells.is_empty() { + current_shell -= 1; + } + + if shells.is_empty() { + std::process::exit(0); + } else { + let new_path = shells[current_shell].clone(); + + stack.add_env_var( + "NUSHELL_SHELLS".into(), + Value::List { + vals: shells, + span: call.head, + }, + ); + stack.add_env_var( + "NUSHELL_CURRENT_SHELL".into(), + Value::Int { + val: current_shell as i64, + span: call.head, + }, + ); + + stack.add_env_var("PWD".into(), new_path); + + Ok(PipelineData::new(call.head)) + } } } diff --git a/crates/nu-command/src/shells/g.rs b/crates/nu-command/src/shells/g.rs new file mode 100644 index 0000000000..64bc10169e --- /dev/null +++ b/crates/nu-command/src/shells/g.rs @@ -0,0 +1,78 @@ +use nu_engine::{current_dir, CallExt}; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Value}; + +/// Source a file for environment variables. +#[derive(Clone)] +pub struct GotoShell; + +impl Command for GotoShell { + fn name(&self) -> &str { + "g" + } + + fn signature(&self) -> Signature { + Signature::build("g") + .required( + "shell-number", + SyntaxShape::Int, + "shell number to change to", + ) + .category(Category::Shells) + } + + fn usage(&self) -> &str { + "Switch to a given shell." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let new_shell: Spanned = call.req(engine_state, stack, 0)?; + + let cwd = current_dir(engine_state, stack)?; + let cwd = Value::String { + val: cwd.to_string_lossy().to_string(), + span: call.head, + }; + + let shells = stack.get_env_var(engine_state, "NUSHELL_SHELLS"); + let shells = if let Some(v) = shells { + v.as_list() + .map(|x| x.to_vec()) + .unwrap_or_else(|_| vec![cwd]) + } else { + vec![cwd] + }; + + let new_path = if let Some(v) = shells.get(new_shell.item as usize) { + v.clone() + } else { + return Err(ShellError::NotFound(new_shell.span)); + }; + + stack.add_env_var( + "NUSHELL_SHELLS".into(), + Value::List { + vals: shells, + span: call.head, + }, + ); + stack.add_env_var( + "NUSHELL_CURRENT_SHELL".into(), + Value::Int { + val: new_shell.item, + span: call.head, + }, + ); + + stack.add_env_var("PWD".into(), new_path); + + Ok(PipelineData::new(call.head)) + } +} diff --git a/crates/nu-command/src/shells/mod.rs b/crates/nu-command/src/shells/mod.rs index 11d875e645..12d4f15fee 100644 --- a/crates/nu-command/src/shells/mod.rs +++ b/crates/nu-command/src/shells/mod.rs @@ -1,3 +1,13 @@ +mod enter; mod exit; +mod g; +mod n; +mod p; +mod shells_; +pub use enter::Enter; pub use exit::Exit; +pub use g::GotoShell; +pub use n::NextShell; +pub use p::PrevShell; +pub use shells_::Shells; diff --git a/crates/nu-command/src/shells/n.rs b/crates/nu-command/src/shells/n.rs new file mode 100644 index 0000000000..9354c7595c --- /dev/null +++ b/crates/nu-command/src/shells/n.rs @@ -0,0 +1,79 @@ +use nu_engine::current_dir; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, PipelineData, ShellError, Signature, Value}; + +/// Source a file for environment variables. +#[derive(Clone)] +pub struct NextShell; + +impl Command for NextShell { + fn name(&self) -> &str { + "n" + } + + fn signature(&self) -> Signature { + Signature::build("n").category(Category::Shells) + } + + fn usage(&self) -> &str { + "Switch to the next shell." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let cwd = current_dir(engine_state, stack)?; + let cwd = Value::String { + val: cwd.to_string_lossy().to_string(), + span: call.head, + }; + + let shells = stack.get_env_var(engine_state, "NUSHELL_SHELLS"); + let 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 mut current_shell = if let Some(v) = current_shell { + v.as_integer().unwrap_or_default() as usize + } else { + 0 + }; + + current_shell += 1; + + if current_shell == shells.len() { + current_shell = 0; + } + + let new_path = shells[current_shell].clone(); + + stack.add_env_var( + "NUSHELL_SHELLS".into(), + Value::List { + vals: shells, + span: call.head, + }, + ); + stack.add_env_var( + "NUSHELL_CURRENT_SHELL".into(), + Value::Int { + val: current_shell as i64, + span: call.head, + }, + ); + + stack.add_env_var("PWD".into(), new_path); + + Ok(PipelineData::new(call.head)) + } +} diff --git a/crates/nu-command/src/shells/p.rs b/crates/nu-command/src/shells/p.rs new file mode 100644 index 0000000000..bd04b82b74 --- /dev/null +++ b/crates/nu-command/src/shells/p.rs @@ -0,0 +1,79 @@ +use nu_engine::current_dir; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, PipelineData, ShellError, Signature, Value}; + +/// Source a file for environment variables. +#[derive(Clone)] +pub struct PrevShell; + +impl Command for PrevShell { + fn name(&self) -> &str { + "p" + } + + fn signature(&self) -> Signature { + Signature::build("p").category(Category::Shells) + } + + fn usage(&self) -> &str { + "Switch to the previous shell." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let cwd = current_dir(engine_state, stack)?; + let cwd = Value::String { + val: cwd.to_string_lossy().to_string(), + span: call.head, + }; + + let shells = stack.get_env_var(engine_state, "NUSHELL_SHELLS"); + let 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 mut current_shell = if let Some(v) = current_shell { + v.as_integer().unwrap_or_default() as usize + } else { + 0 + }; + + if current_shell == 0 { + current_shell = shells.len() - 1; + } else { + current_shell -= 1; + } + + let new_path = shells[current_shell].clone(); + + stack.add_env_var( + "NUSHELL_SHELLS".into(), + Value::List { + vals: shells, + span: call.head, + }, + ); + stack.add_env_var( + "NUSHELL_CURRENT_SHELL".into(), + Value::Int { + val: current_shell as i64, + span: call.head, + }, + ); + + stack.add_env_var("PWD".into(), new_path); + + Ok(PipelineData::new(call.head)) + } +} diff --git a/crates/nu-command/src/shells/shells_.rs b/crates/nu-command/src/shells/shells_.rs new file mode 100644 index 0000000000..b50a500884 --- /dev/null +++ b/crates/nu-command/src/shells/shells_.rs @@ -0,0 +1,72 @@ +use nu_engine::current_dir; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Value, +}; + +/// Source a file for environment variables. +#[derive(Clone)] +pub struct Shells; + +impl Command for Shells { + fn name(&self) -> &str { + "shells" + } + + fn signature(&self) -> Signature { + Signature::build("shells").category(Category::Shells) + } + + fn usage(&self) -> &str { + "Lists all open shells." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let span = call.head; + let cwd = current_dir(engine_state, stack)?; + let cwd = Value::String { + val: cwd.to_string_lossy().to_string(), + span, + }; + + let shells = stack.get_env_var(engine_state, "NUSHELL_SHELLS"); + let 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_integer().unwrap_or_default() as usize + } else { + 0 + }; + + let output = shells + .into_iter() + .enumerate() + .map(move |(idx, val)| Value::Record { + cols: vec!["active".to_string(), "path".to_string()], + vals: vec![ + Value::Bool { + val: idx == current_shell, + span, + }, + val, + ], + span, + }); + + Ok(output.into_pipeline_data(None)) + } +} diff --git a/src/main.rs b/src/main.rs index 5af6a4d5f8..722a188b63 100644 --- a/src/main.rs +++ b/src/main.rs @@ -456,18 +456,20 @@ fn main() -> Result<()> { span: Span { start: 0, end: 0 }, }, ); + } else { + trace!("eval source: {}", s); - continue; + eval_source( + &mut engine_state, + &mut stack, + &s, + &format!("entry #{}", entry_num), + ); } - - trace!("eval source: {}", s); - - eval_source( - &mut engine_state, - &mut stack, - &s, - &format!("entry #{}", entry_num), - ); + // FIXME: permanent state changes like this hopefully in time can be removed + // and be replaced by just passing the cwd in where needed + let cwd = nu_engine::env::current_dir_str(&engine_state, &stack)?; + let _ = std::env::set_current_dir(cwd); } Ok(Signal::CtrlC) => { // `Reedline` clears the line content. New prompt is shown