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
29 changed files with 759 additions and 224 deletions

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
}
}