diff --git a/Cargo.lock b/Cargo.lock index 1f71414b6..6d0388aa2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 7a20398f9..fbfeb34e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index 3831cea61..dfdc89940 100644 --- a/README.md +++ b/README.md @@ -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 | diff --git a/src/cli.rs b/src/cli.rs index 7ab43567d..f9223f66e 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -161,9 +161,12 @@ pub async fn cli() -> Result<(), Box> { 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> { 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 = 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> { 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, diff --git a/src/commands.rs b/src/commands.rs index 862458c0b..6f551421f 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -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; diff --git a/src/commands/cd.rs b/src/commands/cd.rs index dd4fca339..ed14c8baf 100644 --- a/src/commands/cd.rs +++ b/src/commands/cd.rs @@ -1,52 +1,6 @@ use crate::errors::ShellError; use crate::prelude::*; -use std::env; pub fn cd(args: CommandArgs) -> Result { - 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) } diff --git a/src/commands/classified.rs b/src/commands/classified.rs index c09fdfc2b..69807d53a 100644 --- a/src/commands/classified.rs +++ b/src/commands/classified.rs @@ -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, diff --git a/src/commands/command.rs b/src/commands/command.rs index 645a6bca5..d83feebfc 100644 --- a/src/commands/command.rs +++ b/src/commands/command.rs @@ -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>, - pub env: Arc>, + 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> for ReturnValue { } impl ReturnSuccess { - pub fn change_cwd(path: PathBuf) -> ReturnValue { + pub fn change_cwd(path: String) -> ReturnValue { Ok(ReturnSuccess::Action(CommandAction::ChangePath(path))) } diff --git a/src/commands/cp.rs b/src/commands/cp.rs index d0fc343d9..f1acfc506 100644 --- a/src/commands/cp.rs +++ b/src/commands/cp.rs @@ -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 { - 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(); diff --git a/src/commands/enter.rs b/src/commands/enter.rs new file mode 100644 index 000000000..987a7115c --- /dev/null +++ b/src/commands/enter.rs @@ -0,0 +1,21 @@ +use crate::commands::command::CommandAction; +use crate::errors::ShellError; +use crate::prelude::*; + +pub fn enter(args: CommandArgs) -> Result { + //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()) +} diff --git a/src/commands/exit.rs b/src/commands/exit.rs index 64cddfcfb..6e33e3d7d 100644 --- a/src/commands/exit.rs +++ b/src/commands/exit.rs @@ -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 { - Ok(vec![Ok(ReturnSuccess::Action(CommandAction::Exit))].into()) +pub struct Exit; + +impl Command for Exit { + fn run(&self, args: CommandArgs) -> Result { + exit(args) + } + + fn name(&self) -> &str { + "exit" + } + + fn config(&self) -> CommandConfig { + let mut named: IndexMap = 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 { + if args.call_info.args.has("now") { + Ok(vec![Ok(ReturnSuccess::Action(CommandAction::Exit))].into()) + } else { + Ok(vec![Ok(ReturnSuccess::Action(CommandAction::LeaveShell))].into()) + } } diff --git a/src/commands/ls.rs b/src/commands/ls.rs index edbd861fc..c591c1fb2 100644 --- a/src/commands/ls.rs +++ b/src/commands/ls.rs @@ -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 { - 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) } diff --git a/src/commands/next.rs b/src/commands/next.rs new file mode 100644 index 000000000..5f0a714d1 --- /dev/null +++ b/src/commands/next.rs @@ -0,0 +1,7 @@ +use crate::commands::command::CommandAction; +use crate::errors::ShellError; +use crate::prelude::*; + +pub fn next(_args: CommandArgs) -> Result { + Ok(vec![Ok(ReturnSuccess::Action(CommandAction::NextShell))].into()) +} diff --git a/src/commands/open.rs b/src/commands/open.rs index 70059ccf9..fc754c6ab 100644 --- a/src/commands/open.rs +++ b/src/commands/open.rs @@ -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); diff --git a/src/commands/prev.rs b/src/commands/prev.rs new file mode 100644 index 000000000..38f62b386 --- /dev/null +++ b/src/commands/prev.rs @@ -0,0 +1,7 @@ +use crate::commands::command::CommandAction; +use crate::errors::ShellError; +use crate::prelude::*; + +pub fn prev(_args: CommandArgs) -> Result { + Ok(vec![Ok(ReturnSuccess::Action(CommandAction::PreviousShell))].into()) +} diff --git a/src/commands/rm.rs b/src/commands/rm.rs index 578ce4ee6..aa0d10c43 100644 --- a/src/commands/rm.rs +++ b/src/commands/rm.rs @@ -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 { - 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) diff --git a/src/commands/save.rs b/src/commands/save.rs index fa3252a2f..0b73e5898 100644 --- a/src/commands/save.rs +++ b/src/commands/save.rs @@ -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 = 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") { diff --git a/src/commands/shells.rs b/src/commands/shells.rs new file mode 100644 index 000000000..6fdc8a742 --- /dev/null +++ b/src/commands/shells.rs @@ -0,0 +1,18 @@ +use crate::errors::ShellError; +use crate::object::TaggedDictBuilder; +use crate::prelude::*; + +pub fn shells(args: CommandArgs) -> Result { + 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()) +} diff --git a/src/context.rs b/src/context.rs index 377d7f468..73c3b2891 100644 --- a/src/context.rs +++ b/src/context.rs @@ -38,7 +38,7 @@ pub struct Context { sinks: IndexMap>, crate source_map: SourceMap, crate host: Arc>, - crate env: Arc>, + 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> { - self.commands.clone() - } - crate fn has_command(&self, name: &str) -> bool { self.commands.contains_key(name) } @@ -118,7 +114,7 @@ impl Context { ) -> Result { 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, diff --git a/src/env.rs b/src/env.rs index cc571b836..2dd836f1f 100644 --- a/src/env.rs +++ b/src/env.rs @@ -1,5 +1,3 @@ -crate mod environment; crate mod host; -crate use self::environment::Environment; crate use self::host::Host; diff --git a/src/env/environment.rs b/src/env/environment.rs deleted file mode 100644 index f2db354ae..000000000 --- a/src/env/environment.rs +++ /dev/null @@ -1,18 +0,0 @@ -use std::path::{Path, PathBuf}; - -#[derive(Debug, Clone)] -pub struct Environment { - crate path: PathBuf, -} - -impl Environment { - pub fn basic() -> Result { - let path = std::env::current_dir()?; - - Ok(Environment { path }) - } - - pub fn path(&self) -> &Path { - self.path.as_path() - } -} diff --git a/src/prelude.rs b/src/prelude.rs index 56a655bea..9861cbb07 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -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; diff --git a/src/shell.rs b/src/shell.rs index 7a87fa0a9..11f1abd6c 100644 --- a/src/shell.rs +++ b/src/shell.rs @@ -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; diff --git a/src/shell/completer.rs b/src/shell/completer.rs index bcd8577af..191838ff1 100644 --- a/src/shell/completer.rs +++ b/src/shell/completer.rs @@ -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>, + //pub commands: indexmap::IndexMap>, } impl Completer for NuCompleter { @@ -19,7 +18,7 @@ impl Completer for NuCompleter { pos: usize, context: &rustyline::Context, ) -> rustyline::Result<(usize, Vec)> { - let commands: Vec = self.commands.keys().cloned().collect(); + //let commands: Vec = 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)) } diff --git a/src/shell/filesystem_shell.rs b/src/shell/filesystem_shell.rs new file mode 100644 index 000000000..fab9275dd --- /dev/null +++ b/src/shell/filesystem_shell.rs @@ -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 { + 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 { + 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 { + 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 { + 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), ReadlineError> { + self.completer.complete(line, pos, ctx) + } +} + +impl Hinter for FilesystemShell { + fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option { + self.hinter.hint(line, pos, ctx) + } +} diff --git a/src/shell/helper.rs b/src/shell/helper.rs index 486ee2581..7db29c1f2 100644 --- a/src/shell/helper.rs +++ b/src/shell/helper.rs @@ -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>) -> 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), 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 { - self.hinter.hint(line, pos, ctx) + self.helper.hint(line, pos, ctx) } } diff --git a/src/shell/shell.rs b/src/shell/shell.rs new file mode 100644 index 000000000..e489e8721 --- /dev/null +++ b/src/shell/shell.rs @@ -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, + Self: Hinter, +{ + fn name(&self) -> String; + fn ls(&self, call_info: CallInfo, input: InputStream) -> Result; + fn cd(&self, call_info: CallInfo, input: InputStream) -> Result; + fn path(&self) -> String; + fn set_path(&mut self, path: String); +} diff --git a/src/shell/shell_manager.rs b/src/shell/shell_manager.rs new file mode 100644 index 000000000..ab68a79bc --- /dev/null +++ b/src/shell/shell_manager.rs @@ -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>>>, +} + +impl ShellManager { + pub fn basic() -> Result> { + Ok(ShellManager { + shells: Arc::new(Mutex::new(vec![Box::new(FilesystemShell::basic()?)])), + }) + } + + pub fn push(&mut self, shell: Box) { + 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), ReadlineError> { + self.shells + .lock() + .unwrap() + .last() + .unwrap() + .complete(line, pos, ctx) + } + + pub fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option { + 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 { + let env = self.shells.lock().unwrap(); + + env.last().unwrap().ls(call_info, input) + } + pub fn cd(&self, call_info: CallInfo, input: InputStream) -> Result { + let env = self.shells.lock().unwrap(); + + env.last().unwrap().cd(call_info, input) + } +} diff --git a/src/shell/value_shell.rs b/src/shell/value_shell.rs new file mode 100644 index 000000000..bc21761c9 --- /dev/null +++ b/src/shell/value_shell.rs @@ -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, +} + +impl ValueShell { + pub fn new(value: Tagged) -> ValueShell { + ValueShell { + path: "/".to_string(), + value, + } + } + fn members(&self) -> VecDeque> { + 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 { + Ok(self + .members() + .map(|x| ReturnSuccess::value(x)) + .to_output_stream()) + } + + fn cd(&self, call_info: CallInfo, _input: InputStream) -> Result { + 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), 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 { + None + } +}