Multi shells (#254)

Add multi-shells
This commit is contained in:
Jonathan Turner 2019-08-08 05:49:11 +12:00 committed by GitHub
parent bb50f1eb14
commit c231dd32cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 759 additions and 224 deletions

40
Cargo.lock generated
View File

@ -443,20 +443,20 @@ dependencies = [
[[package]]
name = "crossterm"
version = "0.10.1"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossterm_cursor 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"crossterm_input 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"crossterm_screen 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
"crossterm_style 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"crossterm_terminal 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"crossterm_cursor 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
"crossterm_input 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"crossterm_screen 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"crossterm_style 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"crossterm_terminal 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
"crossterm_utils 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossterm_cursor"
version = "0.2.5"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossterm_utils 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
@ -466,10 +466,10 @@ dependencies = [
[[package]]
name = "crossterm_input"
version = "0.3.8"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossterm_screen 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
"crossterm_screen 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"crossterm_utils 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
"crossterm_winapi 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)",
@ -478,7 +478,7 @@ dependencies = [
[[package]]
name = "crossterm_screen"
version = "0.2.4"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossterm_utils 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
@ -488,7 +488,7 @@ dependencies = [
[[package]]
name = "crossterm_style"
version = "0.4.0"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossterm_utils 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
@ -498,10 +498,10 @@ dependencies = [
[[package]]
name = "crossterm_terminal"
version = "0.2.5"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossterm_cursor 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"crossterm_cursor 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
"crossterm_utils 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
"crossterm_winapi 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1804,7 +1804,7 @@ dependencies = [
"chrono-tz 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"clipboard 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"crossterm 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
"crossterm 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"csv 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"derive-new 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)",
@ -3623,12 +3623,12 @@ dependencies = [
"checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b"
"checksum crossbeam-utils 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "677d453a17e8bd2b913fa38e8b9cf04bcdbb5be790aa294f2389661d72036015"
"checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6"
"checksum crossterm 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d94cd758100e3728e5b9a8b9e2d7f21d6f5babf571770514a9cba5448485df18"
"checksum crossterm_cursor 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "10235efee04a9d6cb3e98a46714da3b30bf4ed6210c02ab3bab33cdf10f74e63"
"checksum crossterm_input 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "a49e74609fe693d994a41b729054dbfb41d2c5fa14d8457113bdfeab28973315"
"checksum crossterm_screen 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "957112221da964bd743451559a62def9b58392747a4676ae8cb2a0fd181d8337"
"checksum crossterm_style 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5462cb56aa9572c5e3c1911213da2f9eb23f636253e932e73e7e2e97eef7ddda"
"checksum crossterm_terminal 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1a0a100ca73011f81ddab21c7ffc0b57ac0a3e459fb3874520e41d522321c102"
"checksum crossterm 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9abce7d7c50e9823ea0c0dbeb8f16d7e247af06d75b4c6244ea0a0998b3a6f35"
"checksum crossterm_cursor 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fb4bfd085f17d83e6cd2943f0150d3b4331e465de8dba1750d1966192faf63dc"
"checksum crossterm_input 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c6dd255ca05a596bae31ec392fdb67a829509bb767213f00f37c6b62814db663"
"checksum crossterm_screen 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0bf294484fc34c22d514c41afc0b97ce74e10ea54d6eb5fe4806d1e1ac0f7b76"
"checksum crossterm_style 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8b950f8262e29a446a8a976e0290b67a9067ddc9620f9fb37961d2377f0d8c09"
"checksum crossterm_terminal 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "db8546b519e0c26aa1f43a4a4ea45ccb41eaca74b9a753ea1788f9ad90212636"
"checksum crossterm_utils 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f874a71b2040c730669ddff805c9bc2a1a2f6de9d7f6aab2ae8d29ccbf8a0617"
"checksum crossterm_winapi 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b055e7cc627c452e6a9b977022f48a2db6f0ff73df446ca970f95eef9c381d45"
"checksum csv 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37519ccdfd73a75821cac9319d4fce15a81b9fcf75f951df5b9988aa3a0af87d"

View File

@ -33,13 +33,13 @@ futures-sink-preview = "=0.3.0-alpha.17"
futures_codec = "0.2.5"
term = "0.5.2"
bytes = "0.4.12"
log = "0.4.7"
log = "0.4.8"
pretty_env_logger = "0.3.0"
serde = "1.0.97"
serde = "1.0.98"
serde_json = "1.0.40"
serde-hjson = "0.9.0"
serde_yaml = "0.8"
serde_derive = "1.0.97"
serde_derive = "1.0.98"
serde_bytes = "0.11.1"
getset = "0.0.7"
logos = "0.10.0-rc2"
@ -73,9 +73,9 @@ regex = "1.2.0"
pretty-hex = "0.1.0"
neso = "0.5.0"
rawkey = "0.1.2"
crossterm = "0.10.1"
crossterm = "0.10.2"
tempfile = "3.1.0"
image = "0.22.0"
image = "0.22.1"
semver = "0.9.0"
uuid = {version = "0.7.4", features = [ "v4", "serde" ]}
syntect = "3.2.0"

View File

@ -79,7 +79,7 @@ We can pipeline this into a command that gets the contents of one of the columns
-------------+----------------------------+---------+---------+------+---------
authors | description | edition | license | name | version
-------------+----------------------------+---------+---------+------+---------
[list List] | A shell for the GitHub era | 2018 | MIT | nu | 0.1.2
[list List] | A shell for the GitHub era | 2018 | MIT | nu | 0.1.3
-------------+----------------------------+---------+---------+------+---------
```
@ -87,11 +87,18 @@ Finally, we can use commands outside of Nu once we have the data we want:
```
/home/jonathan/Source/nushell(master)> open Cargo.toml | get package.version | echo $it
0.1.2
0.1.3
```
Here we use the variable `$it` to refer to the value being piped to the external command.
## Shells
By default, Nu will work inside of a single directory and allow you to navigate around your filesystem. Sometimes, you're working in multiple directories at the same time. For this, Nu offers a way of adding additional working directories that you can jump between.
To do so, use the `enter` command, which will allow you create a new shell and enter it at the specified path. You can toggle between this new shell and the original shell with the `p` (for previous) and `n` (for next), allowing you to navigate around a ring buffer of shells. Once you're done with a shell, you can `exit` it and remove it from the ring buffer.
Finally, to get a list of all the current shells, you can use the `shells` command.
## Plugins
@ -127,7 +134,11 @@ Nu adheres closely to a set of goals that make up its design philosophy. As feat
| sys | View information about the current system |
| open {filename or url} | Load a file into a cell, convert to table if possible (avoid by appending '--raw') |
| rm {file or directory} | Remove a file, (for removing directory append '--recursive') |
| exit | Exit the shell |
| exit (--now) | Exit the current shell (or all shells) |
| enter (path) | Create a new shell and begin at this path |
| p | Go to previous shell |
| n | Go to next shell |
| shells | Display the list of current shells |
## Filters on tables (structured data)
| command | description |

View File

@ -161,9 +161,12 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
command("from-xml", Box::new(from_xml::from_xml)),
command("from-yaml", Box::new(from_yaml::from_yaml)),
command("get", Box::new(get::get)),
command("exit", Box::new(exit::exit)),
command("enter", Box::new(enter::enter)),
command("n", Box::new(next::next)),
command("p", Box::new(prev::prev)),
command("lines", Box::new(lines::lines)),
command("pick", Box::new(pick::pick)),
command("shells", Box::new(shells::shells)),
command("split-column", Box::new(split_column::split_column)),
command("split-row", Box::new(split_row::split_row)),
command("lines", Box::new(lines::lines)),
@ -182,29 +185,30 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
Arc::new(Date),
Arc::new(Where),
Arc::new(Config),
Arc::new(Exit),
Arc::new(SkipWhile),
]);
context.add_sinks(vec![
sink("autoview", Box::new(autoview::autoview)),
sink("clip", Box::new(clip::clip)),
sink("save", Box::new(save::save)),
sink("table", Box::new(table::table)),
sink("vtable", Box::new(vtable::vtable)),
Arc::new(Save),
]);
}
let _ = load_plugins(&mut context);
let config = Config::builder().color_mode(ColorMode::Forced).build();
let h = crate::shell::Helper::new(context.clone_commands());
let mut rl: Editor<crate::shell::Helper> = Editor::with_config(config);
//let h = crate::shell::Helper::new(context.clone_commands());
let mut rl: Editor<_> = Editor::with_config(config);
#[cfg(windows)]
{
let _ = ansi_term::enable_ansi_support();
}
rl.set_helper(Some(h));
//rl.set_helper(Some(h));
let _ = rl.load_history("history.txt");
let ctrl_c = Arc::new(AtomicBool::new(false));
@ -220,10 +224,12 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
continue;
}
let cwd = {
let env = context.env.lock().unwrap();
env.path().display().to_string()
};
let cwd = context.shell_manager.path();
rl.set_helper(Some(crate::shell::Helper::new(
context.shell_manager.clone(),
)));
let readline = rl.readline(&format!(
"{}{}> ",
cwd,

View File

@ -10,6 +10,7 @@ crate mod command;
crate mod config;
crate mod cp;
crate mod date;
crate mod enter;
crate mod exit;
crate mod first;
crate mod from_csv;
@ -21,13 +22,16 @@ crate mod from_yaml;
crate mod get;
crate mod lines;
crate mod ls;
crate mod next;
crate mod open;
crate mod pick;
crate mod plugin;
crate mod prev;
crate mod ps;
crate mod reject;
crate mod rm;
crate mod save;
crate mod shells;
crate mod size;
crate mod skip_while;
crate mod sort_by;
@ -48,7 +52,9 @@ crate use command::command;
crate use config::Config;
crate use cp::Copycp;
crate use date::Date;
crate use exit::Exit;
crate use open::Open;
crate use rm::Remove;
crate use save::Save;
crate use skip_while::SkipWhile;
crate use where_::Where;

View File

@ -1,52 +1,6 @@
use crate::errors::ShellError;
use crate::prelude::*;
use std::env;
pub fn cd(args: CommandArgs) -> Result<OutputStream, ShellError> {
let env = args.env.lock().unwrap();
let cwd = env.path().to_path_buf();
let path = match args.nth(0) {
None => match dirs::home_dir() {
Some(o) => o,
_ => {
return Err(ShellError::labeled_error(
"Can not change to home directory",
"can not go to home",
args.call_info.name_span,
))
}
},
Some(v) => {
let target = v.as_string()?;
match dunce::canonicalize(cwd.join(target).as_path()) {
Ok(p) => p,
Err(_) => {
return Err(ShellError::labeled_error(
"Can not change to directory",
"directory not found",
v.span().clone(),
));
}
}
}
};
let mut stream = VecDeque::new();
match env::set_current_dir(&path) {
Ok(_) => {}
Err(_) => {
if args.len() > 0 {
return Err(ShellError::labeled_error(
"Can not change to directory",
"directory not found",
args.nth(0).unwrap().span().clone(),
));
} else {
return Err(ShellError::string("Can not change to directory"));
}
}
}
stream.push_back(ReturnSuccess::change_cwd(path));
Ok(stream.into())
args.shell_manager.cd(args.call_info, args.input)
}

View File

@ -145,12 +145,62 @@ impl InternalCommand {
match item? {
ReturnSuccess::Action(action) => match action {
CommandAction::ChangePath(path) => {
context.env.lock().unwrap().path = path;
context.shell_manager.set_path(path);
}
CommandAction::AddSpanSource(uuid, span_source) => {
context.add_span_source(uuid, span_source);
}
CommandAction::Exit => std::process::exit(0),
CommandAction::EnterShell(location) => {
let path = std::path::Path::new(&location);
if path.is_dir() {
// If it's a directory, add a new filesystem shell
context
.shell_manager
.push(Box::new(FilesystemShell::with_location(location)?));
} else {
// If it's a file, attempt to open the file as a value and enter it
let cwd = context.shell_manager.path();
let full_path = std::path::PathBuf::from(cwd);
let (file_extension, contents, contents_tag, _) =
crate::commands::open::fetch(
&full_path,
&location,
Span::unknown(),
)?;
match contents {
Value::Primitive(Primitive::String(string)) => {
let value = crate::commands::open::parse_as_value(
file_extension,
string,
contents_tag,
Span::unknown(),
)?;
context.shell_manager.push(Box::new(ValueShell::new(value)));
}
value => context
.shell_manager
.push(Box::new(ValueShell::new(value.tagged(Tag::unknown())))),
}
}
}
CommandAction::PreviousShell => {
context.shell_manager.prev();
}
CommandAction::NextShell => {
context.shell_manager.next();
}
CommandAction::LeaveShell => {
context.shell_manager.pop();
if context.shell_manager.is_empty() {
std::process::exit(0);
}
}
},
ReturnSuccess::Value(v) => {
@ -298,7 +348,7 @@ impl ExternalCommand {
process = Exec::shell(new_arg_string);
}
process = process.cwd(context.env.lock().unwrap().path());
process = process.cwd(context.shell_manager.path());
let mut process = match stream_next {
StreamNext::Last => process,

View File

@ -6,7 +6,6 @@ use crate::parser::registry::{self, Args};
use crate::prelude::*;
use getset::Getters;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use uuid::Uuid;
#[derive(Deserialize, Serialize, Debug, Clone)]
@ -20,7 +19,7 @@ pub struct CallInfo {
#[get = "crate"]
pub struct CommandArgs {
pub host: Arc<Mutex<dyn Host + Send>>,
pub env: Arc<Mutex<Environment>>,
pub shell_manager: ShellManager,
pub call_info: CallInfo,
pub input: InputStream,
}
@ -60,9 +59,13 @@ pub struct SinkCommandArgs {
#[derive(Debug, Serialize, Deserialize)]
pub enum CommandAction {
ChangePath(PathBuf),
ChangePath(String),
AddSpanSource(Uuid, SpanSource),
Exit,
EnterShell(String),
PreviousShell,
NextShell,
LeaveShell,
}
#[derive(Debug, Serialize, Deserialize)]
@ -80,7 +83,7 @@ impl From<Tagged<Value>> for ReturnValue {
}
impl ReturnSuccess {
pub fn change_cwd(path: PathBuf) -> ReturnValue {
pub fn change_cwd(path: String) -> ReturnValue {
Ok(ReturnSuccess::Action(CommandAction::ChangePath(path)))
}

View File

@ -3,7 +3,7 @@ use crate::parser::hir::SyntaxType;
use crate::parser::registry::{CommandConfig, NamedType, PositionalType};
use crate::prelude::*;
use indexmap::IndexMap;
use std::path::Path;
use std::path::{Path, PathBuf};
pub struct Copycp;
@ -32,8 +32,8 @@ impl Command for Copycp {
}
pub fn cp(args: CommandArgs) -> Result<OutputStream, ShellError> {
let mut source = args.env.lock().unwrap().path().to_path_buf();
let mut destination = args.env.lock().unwrap().path().to_path_buf();
let mut source = PathBuf::from(args.shell_manager.path());
let mut destination = PathBuf::from(args.shell_manager.path());
let mut dst = String::new();

21
src/commands/enter.rs Normal file
View File

@ -0,0 +1,21 @@
use crate::commands::command::CommandAction;
use crate::errors::ShellError;
use crate::prelude::*;
pub fn enter(args: CommandArgs) -> Result<OutputStream, ShellError> {
//TODO: We could also enter a value in the stream
if args.len() == 0 {
return Err(ShellError::labeled_error(
"Enter requires a path",
"needs parameter",
args.call_info.name_span,
));
}
let location = args.expect_nth(0)?.as_string()?;
Ok(vec![Ok(ReturnSuccess::Action(CommandAction::EnterShell(
location,
)))]
.into())
}

View File

@ -1,7 +1,39 @@
use crate::commands::command::CommandAction;
use crate::errors::ShellError;
use crate::parser::registry::{CommandConfig, NamedType};
use crate::prelude::*;
use indexmap::IndexMap;
pub fn exit(_args: CommandArgs) -> Result<OutputStream, ShellError> {
Ok(vec![Ok(ReturnSuccess::Action(CommandAction::Exit))].into())
pub struct Exit;
impl Command for Exit {
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
exit(args)
}
fn name(&self) -> &str {
"exit"
}
fn config(&self) -> CommandConfig {
let mut named: IndexMap<String, NamedType> = IndexMap::new();
named.insert("now".to_string(), NamedType::Switch);
CommandConfig {
name: self.name().to_string(),
positional: vec![],
rest_positional: false,
named,
is_sink: false,
is_filter: false,
}
}
}
pub fn exit(args: CommandArgs) -> Result<OutputStream, ShellError> {
if args.call_info.args.has("now") {
Ok(vec![Ok(ReturnSuccess::Action(CommandAction::Exit))].into())
} else {
Ok(vec![Ok(ReturnSuccess::Action(CommandAction::LeaveShell))].into())
}
}

View File

@ -1,80 +1,6 @@
use crate::errors::ShellError;
use crate::object::dir_entry_dict;
use crate::prelude::*;
use std::path::{Path, PathBuf};
pub fn ls(args: CommandArgs) -> Result<OutputStream, ShellError> {
let env = args.env.lock().unwrap();
let path = env.path.to_path_buf();
let cwd = path.clone();
let mut full_path = PathBuf::from(path);
match &args.nth(0) {
Some(Tagged { item: value, .. }) => full_path.push(Path::new(&value.as_string()?)),
_ => {}
}
let entries = glob::glob(&full_path.to_string_lossy());
if entries.is_err() {
return Err(ShellError::string("Invalid pattern."));
}
let mut shell_entries = VecDeque::new();
let entries: Vec<_> = entries.unwrap().collect();
// If this is a single entry, try to display the contents of the entry if it's a directory
if entries.len() == 1 {
if let Ok(entry) = &entries[0] {
if entry.is_dir() {
let entries = std::fs::read_dir(&full_path);
let entries = match entries {
Err(e) => {
if let Some(s) = args.nth(0) {
return Err(ShellError::labeled_error(
e.to_string(),
e.to_string(),
s.span(),
));
} else {
return Err(ShellError::labeled_error(
e.to_string(),
e.to_string(),
args.call_info.name_span,
));
}
}
Ok(o) => o,
};
for entry in entries {
let entry = entry?;
let filepath = entry.path();
let filename = filepath.strip_prefix(&cwd).unwrap();
let value = dir_entry_dict(
filename,
&entry.metadata()?,
Tag::unknown_origin(args.call_info.name_span),
)?;
shell_entries.push_back(ReturnSuccess::value(value))
}
return Ok(shell_entries.to_output_stream());
}
}
}
// Enumerate the entries from the glob and add each
for entry in entries {
if let Ok(entry) = entry {
let filename = entry.strip_prefix(&cwd).unwrap();
let metadata = std::fs::metadata(&entry)?;
let value = dir_entry_dict(
filename,
&metadata,
Tag::unknown_origin(args.call_info.name_span),
)?;
shell_entries.push_back(ReturnSuccess::value(value))
}
}
Ok(shell_entries.to_output_stream())
args.shell_manager.ls(args.call_info, args.input)
}

7
src/commands/next.rs Normal file
View File

@ -0,0 +1,7 @@
use crate::commands::command::CommandAction;
use crate::errors::ShellError;
use crate::prelude::*;
pub fn next(_args: CommandArgs) -> Result<OutputStream, ShellError> {
Ok(vec![Ok(ReturnSuccess::Action(CommandAction::NextShell))].into())
}

View File

@ -12,11 +12,7 @@ command! {
let span = args.call_info.name_span;
let cwd = args
.env
.lock()
.unwrap()
.path()
.to_path_buf();
.shell_manager.path();
let full_path = PathBuf::from(cwd);

7
src/commands/prev.rs Normal file
View File

@ -0,0 +1,7 @@
use crate::commands::command::CommandAction;
use crate::errors::ShellError;
use crate::prelude::*;
pub fn prev(_args: CommandArgs) -> Result<OutputStream, ShellError> {
Ok(vec![Ok(ReturnSuccess::Action(CommandAction::PreviousShell))].into())
}

View File

@ -5,6 +5,7 @@ use crate::prelude::*;
use glob::glob;
use indexmap::IndexMap;
use std::path::PathBuf;
pub struct Remove;
@ -33,7 +34,7 @@ impl Command for Remove {
}
pub fn rm(args: CommandArgs) -> Result<OutputStream, ShellError> {
let mut full_path = args.env.lock().unwrap().path().to_path_buf();
let mut full_path = PathBuf::from(args.shell_manager.path());
match args
.nth(0)

View File

@ -5,11 +5,40 @@ use crate::commands::to_toml::value_to_toml_value;
use crate::commands::to_yaml::value_to_yaml_value;
use crate::errors::ShellError;
use crate::object::{Primitive, Value};
use crate::parser::registry::{CommandConfig, NamedType};
use crate::prelude::*;
use crate::SpanSource;
use indexmap::IndexMap;
use std::path::{Path, PathBuf};
pub struct Save;
impl Sink for Save {
fn run(&self, args: SinkCommandArgs) -> Result<(), ShellError> {
save(args)
}
fn name(&self) -> &str {
"save"
}
fn config(&self) -> CommandConfig {
let mut named: IndexMap<String, NamedType> = IndexMap::new();
named.insert("raw".to_string(), NamedType::Switch);
CommandConfig {
name: self.name().to_string(),
positional: vec![],
rest_positional: false,
named,
is_sink: false,
is_filter: false,
}
}
}
pub fn save(args: SinkCommandArgs) -> Result<(), ShellError> {
let cwd = args.ctx.env.lock().unwrap().path().to_path_buf();
let cwd = args.ctx.shell_manager.path();
let mut full_path = PathBuf::from(cwd);
let save_raw = if args.call_info.args.has("raw") {

18
src/commands/shells.rs Normal file
View File

@ -0,0 +1,18 @@
use crate::errors::ShellError;
use crate::object::TaggedDictBuilder;
use crate::prelude::*;
pub fn shells(args: CommandArgs) -> Result<OutputStream, ShellError> {
let mut shells_out = VecDeque::new();
let span = args.call_info.name_span;
for shell in args.shell_manager.shells.lock().unwrap().iter() {
let mut dict = TaggedDictBuilder::new(Tag::unknown_origin(span));
dict.insert("name", shell.name());
dict.insert("path", shell.path());
shells_out.push_back(dict.into_tagged_value());
}
Ok(shells_out.to_output_stream())
}

View File

@ -38,7 +38,7 @@ pub struct Context {
sinks: IndexMap<String, Arc<dyn Sink>>,
crate source_map: SourceMap,
crate host: Arc<Mutex<dyn Host + Send>>,
crate env: Arc<Mutex<Environment>>,
crate shell_manager: ShellManager,
}
impl Context {
@ -48,7 +48,7 @@ impl Context {
sinks: indexmap::IndexMap::new(),
source_map: SourceMap::new(),
host: Arc::new(Mutex::new(crate::env::host::BasicHost)),
env: Arc::new(Mutex::new(Environment::basic()?)),
shell_manager: ShellManager::basic()?,
})
}
@ -96,10 +96,6 @@ impl Context {
command.run(command_args)
}
pub fn clone_commands(&self) -> indexmap::IndexMap<String, Arc<dyn Command>> {
self.commands.clone()
}
crate fn has_command(&self, name: &str) -> bool {
self.commands.contains_key(name)
}
@ -118,7 +114,7 @@ impl Context {
) -> Result<OutputStream, ShellError> {
let command_args = CommandArgs {
host: self.host.clone(),
env: self.env.clone(),
shell_manager: self.shell_manager.clone(),
call_info: CallInfo {
name_span,
source_map,

View File

@ -1,5 +1,3 @@
crate mod environment;
crate mod host;
crate use self::environment::Environment;
crate use self::host::Host;

View File

@ -1,18 +0,0 @@
use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
pub struct Environment {
crate path: PathBuf,
}
impl Environment {
pub fn basic() -> Result<Environment, std::io::Error> {
let path = std::env::current_dir()?;
Ok(Environment { path })
}
pub fn path(&self) -> &Path {
self.path.as_path()
}
}

View File

@ -38,11 +38,14 @@ crate use crate::commands::command::{
};
crate use crate::context::{Context, SpanSource};
crate use crate::env::host::handle_unexpected;
crate use crate::env::{Environment, Host};
crate use crate::env::Host;
crate use crate::errors::ShellError;
crate use crate::object::meta::{Tag, Tagged, TaggedItem};
crate use crate::object::types::ExtractType;
crate use crate::object::{Primitive, Value};
crate use crate::shell::filesystem_shell::FilesystemShell;
crate use crate::shell::shell_manager::ShellManager;
crate use crate::shell::value_shell::ValueShell;
crate use crate::stream::{InputStream, OutputStream};
crate use crate::Span;
crate use crate::Text;

View File

@ -1,4 +1,8 @@
crate mod completer;
crate mod filesystem_shell;
crate mod helper;
crate mod shell;
crate mod shell_manager;
crate mod value_shell;
crate use helper::Helper;

View File

@ -1,4 +1,3 @@
use crate::prelude::*;
use derive_new::new;
use rustyline::completion::Completer;
use rustyline::completion::{self, FilenameCompleter};
@ -7,7 +6,7 @@ use rustyline::line_buffer::LineBuffer;
#[derive(new)]
crate struct NuCompleter {
pub file_completer: FilenameCompleter,
pub commands: indexmap::IndexMap<String, Arc<dyn Command>>,
//pub commands: indexmap::IndexMap<String, Arc<dyn Command>>,
}
impl Completer for NuCompleter {
@ -19,7 +18,7 @@ impl Completer for NuCompleter {
pos: usize,
context: &rustyline::Context,
) -> rustyline::Result<(usize, Vec<completion::Pair>)> {
let commands: Vec<String> = self.commands.keys().cloned().collect();
//let commands: Vec<String> = self.commands.keys().cloned().collect();
let mut completions = self.file_completer.complete(line, pos, context)?.1;
@ -50,6 +49,7 @@ impl Completer for NuCompleter {
replace_pos -= 1;
}
/*
for command in commands.iter() {
let mut pos = replace_pos;
let mut matched = true;
@ -73,6 +73,7 @@ impl Completer for NuCompleter {
});
}
}
*/
Ok((replace_pos, completions))
}

View File

@ -0,0 +1,206 @@
use crate::commands::command::CallInfo;
use crate::object::dir_entry_dict;
use crate::prelude::*;
use crate::shell::completer::NuCompleter;
use crate::shell::shell::Shell;
use rustyline::completion::{self, Completer, FilenameCompleter};
use rustyline::error::ReadlineError;
use rustyline::hint::{Hinter, HistoryHinter};
use std::path::{Path, PathBuf};
pub struct FilesystemShell {
crate path: String,
completer: NuCompleter,
hinter: HistoryHinter,
}
impl Clone for FilesystemShell {
fn clone(&self) -> Self {
FilesystemShell {
path: self.path.clone(),
completer: NuCompleter {
file_completer: FilenameCompleter::new(),
},
hinter: HistoryHinter {},
}
}
}
impl FilesystemShell {
pub fn basic() -> Result<FilesystemShell, std::io::Error> {
let path = std::env::current_dir()?;
Ok(FilesystemShell {
path: path.to_string_lossy().to_string(),
completer: NuCompleter {
file_completer: FilenameCompleter::new(),
},
hinter: HistoryHinter {},
})
}
pub fn with_location(path: String) -> Result<FilesystemShell, std::io::Error> {
Ok(FilesystemShell {
path,
completer: NuCompleter {
file_completer: FilenameCompleter::new(),
},
hinter: HistoryHinter {},
})
}
}
impl Shell for FilesystemShell {
fn name(&self) -> String {
"filesystem".to_string()
}
fn ls(&self, call_info: CallInfo, _input: InputStream) -> Result<OutputStream, ShellError> {
let cwd = self.path.clone();
let mut full_path = PathBuf::from(&self.path);
match &call_info.args.nth(0) {
Some(Tagged { item: value, .. }) => full_path.push(Path::new(&value.as_string()?)),
_ => {}
}
let entries = glob::glob(&full_path.to_string_lossy());
if entries.is_err() {
return Err(ShellError::string("Invalid pattern."));
}
let mut shell_entries = VecDeque::new();
let entries: Vec<_> = entries.unwrap().collect();
// If this is a single entry, try to display the contents of the entry if it's a directory
if entries.len() == 1 {
if let Ok(entry) = &entries[0] {
if entry.is_dir() {
let entries = std::fs::read_dir(&full_path);
let entries = match entries {
Err(e) => {
if let Some(s) = call_info.args.nth(0) {
return Err(ShellError::labeled_error(
e.to_string(),
e.to_string(),
s.span(),
));
} else {
return Err(ShellError::labeled_error(
e.to_string(),
e.to_string(),
call_info.name_span,
));
}
}
Ok(o) => o,
};
for entry in entries {
let entry = entry?;
let filepath = entry.path();
let filename = filepath.strip_prefix(&cwd).unwrap();
let value = dir_entry_dict(
filename,
&entry.metadata()?,
Tag::unknown_origin(call_info.name_span),
)?;
shell_entries.push_back(ReturnSuccess::value(value))
}
return Ok(shell_entries.to_output_stream());
}
}
}
// Enumerate the entries from the glob and add each
for entry in entries {
if let Ok(entry) = entry {
let filename = entry.strip_prefix(&cwd).unwrap();
let metadata = std::fs::metadata(&entry)?;
let value = dir_entry_dict(
filename,
&metadata,
Tag::unknown_origin(call_info.name_span),
)?;
shell_entries.push_back(ReturnSuccess::value(value))
}
}
Ok(shell_entries.to_output_stream())
}
fn cd(&self, call_info: CallInfo, _input: InputStream) -> Result<OutputStream, ShellError> {
let path = match call_info.args.nth(0) {
None => match dirs::home_dir() {
Some(o) => o,
_ => {
return Err(ShellError::labeled_error(
"Can not change to home directory",
"can not go to home",
call_info.name_span,
))
}
},
Some(v) => {
let target = v.as_string()?;
let path = PathBuf::from(self.path());
match dunce::canonicalize(path.join(target).as_path()) {
Ok(p) => p,
Err(_) => {
return Err(ShellError::labeled_error(
"Can not change to directory",
"directory not found",
v.span().clone(),
));
}
}
}
};
let mut stream = VecDeque::new();
match std::env::set_current_dir(&path) {
Ok(_) => {}
Err(_) => {
if call_info.args.len() > 0 {
return Err(ShellError::labeled_error(
"Can not change to directory",
"directory not found",
call_info.args.nth(0).unwrap().span().clone(),
));
} else {
return Err(ShellError::string("Can not change to directory"));
}
}
}
stream.push_back(ReturnSuccess::change_cwd(
path.to_string_lossy().to_string(),
));
Ok(stream.into())
}
fn path(&self) -> String {
self.path.clone()
}
fn set_path(&mut self, path: String) {
let _ = std::env::set_current_dir(&path);
self.path = path.clone();
}
}
impl Completer for FilesystemShell {
type Candidate = completion::Pair;
fn complete(
&self,
line: &str,
pos: usize,
ctx: &rustyline::Context<'_>,
) -> Result<(usize, Vec<completion::Pair>), ReadlineError> {
self.completer.complete(line, pos, ctx)
}
}
impl Hinter for FilesystemShell {
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<String> {
self.hinter.hint(line, pos, ctx)
}
}

View File

@ -2,30 +2,22 @@ use crate::parser::nom_input;
use crate::parser::parse::token_tree::TokenNode;
use crate::parser::parse::tokens::RawToken;
use crate::parser::{Pipeline, PipelineElement};
use crate::prelude::*;
use crate::shell::completer::NuCompleter;
use crate::shell::shell_manager::ShellManager;
use crate::Tagged;
use ansi_term::Color;
use rustyline::completion::{self, Completer, FilenameCompleter};
use rustyline::completion::{self, Completer};
use rustyline::error::ReadlineError;
use rustyline::highlight::Highlighter;
use rustyline::hint::{Hinter, HistoryHinter};
use rustyline::hint::Hinter;
use std::borrow::Cow::{self, Owned};
crate struct Helper {
completer: NuCompleter,
hinter: HistoryHinter,
helper: ShellManager,
}
impl Helper {
crate fn new(commands: indexmap::IndexMap<String, Arc<dyn Command>>) -> Helper {
Helper {
completer: NuCompleter {
file_completer: FilenameCompleter::new(),
commands,
},
hinter: HistoryHinter {},
}
crate fn new(helper: ShellManager) -> Helper {
Helper { helper }
}
}
@ -38,13 +30,13 @@ impl Completer for Helper {
pos: usize,
ctx: &rustyline::Context<'_>,
) -> Result<(usize, Vec<completion::Pair>), ReadlineError> {
self.completer.complete(line, pos, ctx)
self.helper.complete(line, pos, ctx)
}
}
impl Hinter for Helper {
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<String> {
self.hinter.hint(line, pos, ctx)
self.helper.hint(line, pos, ctx)
}
}

16
src/shell/shell.rs Normal file
View File

@ -0,0 +1,16 @@
use crate::commands::command::CallInfo;
use crate::errors::ShellError;
use crate::stream::{InputStream, OutputStream};
use rustyline::{completion::Completer, hint::Hinter};
pub trait Shell
where
Self: Completer<Candidate = rustyline::completion::Pair>,
Self: Hinter,
{
fn name(&self) -> String;
fn ls(&self, call_info: CallInfo, input: InputStream) -> Result<OutputStream, ShellError>;
fn cd(&self, call_info: CallInfo, input: InputStream) -> Result<OutputStream, ShellError>;
fn path(&self) -> String;
fn set_path(&mut self, path: String);
}

100
src/shell/shell_manager.rs Normal file
View File

@ -0,0 +1,100 @@
use crate::commands::command::CallInfo;
use crate::errors::ShellError;
use crate::shell::filesystem_shell::FilesystemShell;
use crate::shell::shell::Shell;
use crate::stream::{InputStream, OutputStream};
use rustyline::completion::{self, Completer};
use rustyline::error::ReadlineError;
use std::error::Error;
use std::sync::{Arc, Mutex};
#[derive(Clone)]
pub struct ShellManager {
crate shells: Arc<Mutex<Vec<Box<dyn Shell>>>>,
}
impl ShellManager {
pub fn basic() -> Result<ShellManager, Box<dyn Error>> {
Ok(ShellManager {
shells: Arc::new(Mutex::new(vec![Box::new(FilesystemShell::basic()?)])),
})
}
pub fn push(&mut self, shell: Box<dyn Shell>) {
self.shells.lock().unwrap().push(shell);
self.set_path(self.path());
}
pub fn pop(&mut self) {
self.shells.lock().unwrap().pop();
}
pub fn is_empty(&self) -> bool {
self.shells.lock().unwrap().is_empty()
}
pub fn path(&self) -> String {
self.shells.lock().unwrap().last().unwrap().path()
}
pub fn set_path(&mut self, path: String) {
self.shells
.lock()
.unwrap()
.last_mut()
.unwrap()
.set_path(path)
}
pub fn complete(
&self,
line: &str,
pos: usize,
ctx: &rustyline::Context<'_>,
) -> Result<(usize, Vec<completion::Pair>), ReadlineError> {
self.shells
.lock()
.unwrap()
.last()
.unwrap()
.complete(line, pos, ctx)
}
pub fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<String> {
self.shells
.lock()
.unwrap()
.last()
.unwrap()
.hint(line, pos, ctx)
}
pub fn next(&mut self) {
{
let mut x = self.shells.lock().unwrap();
let shell = x.pop().unwrap();
x.insert(0, shell);
}
self.set_path(self.path());
}
pub fn prev(&mut self) {
{
let mut x = self.shells.lock().unwrap();
let shell = x.remove(0);
x.push(shell);
}
self.set_path(self.path());
}
pub fn ls(&self, call_info: CallInfo, input: InputStream) -> Result<OutputStream, ShellError> {
let env = self.shells.lock().unwrap();
env.last().unwrap().ls(call_info, input)
}
pub fn cd(&self, call_info: CallInfo, input: InputStream) -> Result<OutputStream, ShellError> {
let env = self.shells.lock().unwrap();
env.last().unwrap().cd(call_info, input)
}
}

170
src/shell/value_shell.rs Normal file
View File

@ -0,0 +1,170 @@
use crate::commands::command::CallInfo;
use crate::prelude::*;
use crate::shell::shell::Shell;
use rustyline::completion::{self, Completer};
use rustyline::error::ReadlineError;
use rustyline::hint::Hinter;
use std::ffi::OsStr;
use std::path::PathBuf;
#[derive(Clone)]
pub struct ValueShell {
crate path: String,
crate value: Tagged<Value>,
}
impl ValueShell {
pub fn new(value: Tagged<Value>) -> ValueShell {
ValueShell {
path: "/".to_string(),
value,
}
}
fn members(&self) -> VecDeque<Tagged<Value>> {
let mut shell_entries = VecDeque::new();
let full_path = PathBuf::from(&self.path);
let mut viewed = self.value.clone();
let sep_string = std::path::MAIN_SEPARATOR.to_string();
let sep = OsStr::new(&sep_string);
for p in full_path.iter() {
match p {
x if x == sep => {}
step => match viewed.get_data_by_key(step.to_str().unwrap()) {
Some(v) => {
viewed = v.clone();
}
_ => {}
},
}
}
match viewed {
Tagged {
item: Value::List(l),
..
} => {
for item in l {
shell_entries.push_back(item.clone());
}
}
x => {
shell_entries.push_back(x.clone());
}
}
shell_entries
}
}
impl Shell for ValueShell {
fn name(&self) -> String {
"value".to_string()
}
fn ls(&self, _call_info: CallInfo, _input: InputStream) -> Result<OutputStream, ShellError> {
Ok(self
.members()
.map(|x| ReturnSuccess::value(x))
.to_output_stream())
}
fn cd(&self, call_info: CallInfo, _input: InputStream) -> Result<OutputStream, ShellError> {
let path = match call_info.args.nth(0) {
None => "/".to_string(),
Some(v) => {
let target = v.as_string()?;
let mut cwd = PathBuf::from(&self.path);
match target {
x if x == ".." => {
cwd.pop();
}
_ => match target.chars().nth(0) {
Some(x) if x == '/' => cwd = PathBuf::from(target),
_ => {
cwd.push(target);
}
},
}
cwd.to_string_lossy().to_string()
}
};
let mut stream = VecDeque::new();
stream.push_back(ReturnSuccess::change_cwd(path));
Ok(stream.into())
}
fn path(&self) -> String {
self.path.clone()
}
fn set_path(&mut self, path: String) {
let _ = std::env::set_current_dir(&path);
self.path = path.clone();
}
}
impl Completer for ValueShell {
type Candidate = completion::Pair;
fn complete(
&self,
line: &str,
pos: usize,
_ctx: &rustyline::Context<'_>,
) -> Result<(usize, Vec<completion::Pair>), ReadlineError> {
let mut completions = vec![];
let mut possible_completion = vec![];
let members = self.members();
for member in members {
match member {
Tagged { item, .. } => {
for desc in item.data_descriptors() {
possible_completion.push(desc);
}
}
}
}
let line_chars: Vec<_> = line.chars().collect();
let mut replace_pos = pos;
while replace_pos > 0 {
if line_chars[replace_pos - 1] == ' ' {
break;
}
replace_pos -= 1;
}
for command in possible_completion.iter() {
let mut pos = replace_pos;
let mut matched = true;
if pos < line_chars.len() {
for chr in command.chars() {
if line_chars[pos] != chr {
matched = false;
break;
}
pos += 1;
if pos == line_chars.len() {
break;
}
}
}
if matched {
completions.push(completion::Pair {
display: command.to_string(),
replacement: command.to_string(),
});
}
}
Ok((replace_pos, completions))
}
}
impl Hinter for ValueShell {
fn hint(&self, _line: &str, _pos: usize, _ctx: &rustyline::Context<'_>) -> Option<String> {
None
}
}