Add shells support (#671)

This commit is contained in:
JT 2022-01-05 15:35:50 +11:00 committed by GitHub
parent b4c72e85e1
commit c158d29577
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 504 additions and 17 deletions

View File

@ -181,7 +181,12 @@ pub fn create_default_context(cwd: impl AsRef<Path>) -> EngineState {
// Shells // Shells
bind_command! { bind_command! {
Enter,
Exit, Exit,
GotoShell,
NextShell,
PrevShell,
Shells,
}; };
// Formats // Formats

View File

@ -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<PipelineData, ShellError> {
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))
}
}

View File

@ -1,6 +1,7 @@
use nu_engine::{current_dir, CallExt};
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; 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. /// Source a file for environment variables.
#[derive(Clone)] #[derive(Clone)]
@ -12,7 +13,14 @@ impl Command for Exit {
} }
fn signature(&self) -> Signature { 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 { fn usage(&self) -> &str {
@ -21,13 +29,72 @@ impl Command for Exit {
fn run( fn run(
&self, &self,
_engine_state: &EngineState, engine_state: &EngineState,
_stack: &mut Stack, stack: &mut Stack,
_call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
//TODO: add more shell support let exit_code: Option<i64> = 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))
}
} }
} }

View File

@ -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<PipelineData, ShellError> {
let new_shell: Spanned<i64> = 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))
}
}

View File

@ -1,3 +1,13 @@
mod enter;
mod exit; mod exit;
mod g;
mod n;
mod p;
mod shells_;
pub use enter::Enter;
pub use exit::Exit; pub use exit::Exit;
pub use g::GotoShell;
pub use n::NextShell;
pub use p::PrevShell;
pub use shells_::Shells;

View File

@ -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<PipelineData, ShellError> {
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))
}
}

View File

@ -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<PipelineData, ShellError> {
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))
}
}

View File

@ -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<PipelineData, ShellError> {
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))
}
}

View File

@ -456,18 +456,20 @@ fn main() -> Result<()> {
span: Span { start: 0, end: 0 }, span: Span { start: 0, end: 0 },
}, },
); );
} else {
trace!("eval source: {}", s);
continue; eval_source(
&mut engine_state,
&mut stack,
&s,
&format!("entry #{}", entry_num),
);
} }
// FIXME: permanent state changes like this hopefully in time can be removed
trace!("eval source: {}", s); // and be replaced by just passing the cwd in where needed
let cwd = nu_engine::env::current_dir_str(&engine_state, &stack)?;
eval_source( let _ = std::env::set_current_dir(cwd);
&mut engine_state,
&mut stack,
&s,
&format!("entry #{}", entry_num),
);
} }
Ok(Signal::CtrlC) => { Ok(Signal::CtrlC) => {
// `Reedline` clears the line content. New prompt is shown // `Reedline` clears the line content. New prompt is shown