mirror of
https://github.com/nushell/nushell.git
synced 2025-08-10 12:58:11 +02:00
Move most of the root package into a subcrate. (#1445)
This improves incremental build time when working on what was previously the root package. For example, previously all plugins would be rebuilt with a change to `src/commands/classified/external.rs`, but now only `nu-cli` will have to be rebuilt (and anything that depends on it).
This commit is contained in:
154
crates/nu-cli/src/shell/completer.rs
Normal file
154
crates/nu-cli/src/shell/completer.rs
Normal file
@ -0,0 +1,154 @@
|
||||
use crate::context::CommandRegistry;
|
||||
|
||||
use derive_new::new;
|
||||
use nu_parser::ExpandContext;
|
||||
use nu_source::{HasSpan, Text};
|
||||
use rustyline::completion::{Completer, FilenameCompleter};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(new)]
|
||||
pub(crate) struct NuCompleter {
|
||||
pub file_completer: FilenameCompleter,
|
||||
pub commands: CommandRegistry,
|
||||
pub homedir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl NuCompleter {
|
||||
pub fn complete(
|
||||
&self,
|
||||
line: &str,
|
||||
pos: usize,
|
||||
context: &rustyline::Context,
|
||||
) -> rustyline::Result<(usize, Vec<rustyline::completion::Pair>)> {
|
||||
let text = Text::from(line);
|
||||
let expand_context =
|
||||
ExpandContext::new(Box::new(self.commands.clone()), &text, self.homedir.clone());
|
||||
|
||||
#[allow(unused)]
|
||||
// smarter completions
|
||||
let shapes = nu_parser::pipeline_shapes(line, expand_context);
|
||||
|
||||
let commands: Vec<String> = self.commands.names();
|
||||
|
||||
let line_chars: Vec<_> = line[..pos].chars().collect();
|
||||
|
||||
let mut replace_pos = line_chars.len();
|
||||
while replace_pos > 0 {
|
||||
if line_chars[replace_pos - 1] == ' ' {
|
||||
break;
|
||||
}
|
||||
replace_pos -= 1;
|
||||
}
|
||||
|
||||
let mut completions;
|
||||
|
||||
// See if we're a flag
|
||||
if pos > 0 && replace_pos < line_chars.len() && line_chars[replace_pos] == '-' {
|
||||
completions = self.get_matching_arguments(&line_chars, line, replace_pos, pos);
|
||||
} else {
|
||||
completions = self.file_completer.complete(line, pos, context)?.1;
|
||||
|
||||
for completion in &mut completions {
|
||||
if completion.replacement.contains("\\ ") {
|
||||
completion.replacement = completion.replacement.replace("\\ ", " ");
|
||||
}
|
||||
if completion.replacement.contains("\\(") {
|
||||
completion.replacement = completion.replacement.replace("\\(", "(");
|
||||
}
|
||||
|
||||
if completion.replacement.contains(' ') || completion.replacement.contains('(') {
|
||||
if !completion.replacement.starts_with('\"') {
|
||||
completion.replacement = format!("\"{}", completion.replacement);
|
||||
}
|
||||
if !completion.replacement.ends_with('\"') {
|
||||
completion.replacement = format!("{}\"", completion.replacement);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for command in commands.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(rustyline::completion::Pair {
|
||||
display: command.clone(),
|
||||
replacement: command.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok((replace_pos, completions))
|
||||
}
|
||||
|
||||
fn get_matching_arguments(
|
||||
&self,
|
||||
line_chars: &[char],
|
||||
line: &str,
|
||||
replace_pos: usize,
|
||||
pos: usize,
|
||||
) -> Vec<rustyline::completion::Pair> {
|
||||
let mut matching_arguments = vec![];
|
||||
|
||||
let mut line_copy = line.to_string();
|
||||
let substring = line_chars[replace_pos..pos].iter().collect::<String>();
|
||||
let replace_string = (replace_pos..pos).map(|_| " ").collect::<String>();
|
||||
line_copy.replace_range(replace_pos..pos, &replace_string);
|
||||
|
||||
if let Ok(val) = nu_parser::parse(&line_copy) {
|
||||
let source = Text::from(line);
|
||||
let pipeline_list = vec![val.clone()];
|
||||
|
||||
let expand_context = nu_parser::ExpandContext {
|
||||
homedir: None,
|
||||
registry: Box::new(self.commands.clone()),
|
||||
source: &source,
|
||||
};
|
||||
|
||||
let mut iterator =
|
||||
nu_parser::TokensIterator::new(&pipeline_list, expand_context, val.span());
|
||||
|
||||
let result = iterator.expand_infallible(nu_parser::PipelineShape);
|
||||
|
||||
if result.failed.is_none() {
|
||||
for command in result.commands.list {
|
||||
if let nu_parser::ClassifiedCommand::Internal(nu_parser::InternalCommand {
|
||||
args,
|
||||
..
|
||||
}) = command
|
||||
{
|
||||
if replace_pos >= args.span.start() && replace_pos <= args.span.end() {
|
||||
if let Some(named) = args.named {
|
||||
for (name, _) in named.iter() {
|
||||
let full_flag = format!("--{}", name);
|
||||
|
||||
if full_flag.starts_with(&substring) {
|
||||
matching_arguments.push(rustyline::completion::Pair {
|
||||
display: full_flag.clone(),
|
||||
replacement: full_flag,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
matching_arguments
|
||||
}
|
||||
}
|
1112
crates/nu-cli/src/shell/filesystem_shell.rs
Normal file
1112
crates/nu-cli/src/shell/filesystem_shell.rs
Normal file
File diff suppressed because it is too large
Load Diff
261
crates/nu-cli/src/shell/help_shell.rs
Normal file
261
crates/nu-cli/src/shell/help_shell.rs
Normal file
@ -0,0 +1,261 @@
|
||||
use crate::commands::command::EvaluatedWholeStreamCommandArgs;
|
||||
use crate::commands::cp::CopyArgs;
|
||||
use crate::commands::ls::LsArgs;
|
||||
use crate::commands::mkdir::MkdirArgs;
|
||||
use crate::commands::mv::MoveArgs;
|
||||
use crate::commands::rm::RemoveArgs;
|
||||
use crate::data::command_dict;
|
||||
use crate::prelude::*;
|
||||
use crate::shell::shell::Shell;
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::ExpandContext;
|
||||
use nu_protocol::{
|
||||
Primitive, ReturnSuccess, ShellTypeName, TaggedDictBuilder, UntaggedValue, Value,
|
||||
};
|
||||
use std::ffi::OsStr;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct HelpShell {
|
||||
pub(crate) path: String,
|
||||
pub(crate) value: Value,
|
||||
}
|
||||
|
||||
impl HelpShell {
|
||||
pub fn index(registry: &CommandRegistry) -> Result<HelpShell, ShellError> {
|
||||
let mut cmds = TaggedDictBuilder::new(Tag::unknown());
|
||||
let mut specs = Vec::new();
|
||||
|
||||
for cmd in registry.names() {
|
||||
if let Some(cmd_value) = registry.get_command(&cmd) {
|
||||
let mut spec = TaggedDictBuilder::new(Tag::unknown());
|
||||
let value = command_dict(cmd_value, Tag::unknown());
|
||||
|
||||
spec.insert_untagged("name", cmd);
|
||||
spec.insert_untagged(
|
||||
"description",
|
||||
value
|
||||
.get_data_by_key("usage".spanned_unknown())
|
||||
.ok_or_else(|| {
|
||||
ShellError::untagged_runtime_error(
|
||||
"Internal error: expected to find usage",
|
||||
)
|
||||
})?
|
||||
.as_string()?,
|
||||
);
|
||||
spec.insert_value("details", value);
|
||||
|
||||
specs.push(spec.into_value());
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
cmds.insert_untagged("help", UntaggedValue::Table(specs));
|
||||
|
||||
Ok(HelpShell {
|
||||
path: "/help".to_string(),
|
||||
value: cmds.into_value(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn for_command(cmd: Value, registry: &CommandRegistry) -> Result<HelpShell, ShellError> {
|
||||
let mut sh = HelpShell::index(®istry)?;
|
||||
|
||||
if let Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(name)),
|
||||
..
|
||||
} = cmd
|
||||
{
|
||||
sh.set_path(format!("/help/{:}/details", name));
|
||||
}
|
||||
|
||||
Ok(sh)
|
||||
}
|
||||
|
||||
fn commands(&self) -> VecDeque<Value> {
|
||||
let mut cmds = 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 => {
|
||||
let step: &str = &step.to_string_lossy().to_string();
|
||||
let value = viewed.get_data_by_key(step.spanned_unknown());
|
||||
if let Some(v) = value {
|
||||
viewed = v.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
match viewed {
|
||||
Value {
|
||||
value: UntaggedValue::Table(l),
|
||||
..
|
||||
} => {
|
||||
for item in l {
|
||||
cmds.push_back(item.clone());
|
||||
}
|
||||
}
|
||||
x => {
|
||||
cmds.push_back(x);
|
||||
}
|
||||
}
|
||||
|
||||
cmds
|
||||
}
|
||||
}
|
||||
|
||||
impl Shell for HelpShell {
|
||||
fn name(&self) -> String {
|
||||
let anchor_name = self.value.anchor_name();
|
||||
|
||||
match anchor_name {
|
||||
Some(x) => format!("{{{}}}", x),
|
||||
None => format!("<{}>", self.value.type_name()),
|
||||
}
|
||||
}
|
||||
|
||||
fn homedir(&self) -> Option<PathBuf> {
|
||||
dirs::home_dir()
|
||||
}
|
||||
|
||||
fn path(&self) -> String {
|
||||
self.path.clone()
|
||||
}
|
||||
|
||||
fn pwd(&self, _: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError> {
|
||||
Ok(OutputStream::empty())
|
||||
}
|
||||
|
||||
fn set_path(&mut self, path: String) {
|
||||
let _ = std::env::set_current_dir(&path);
|
||||
self.path = path;
|
||||
}
|
||||
|
||||
fn ls(
|
||||
&self,
|
||||
_args: LsArgs,
|
||||
_context: &RunnablePerItemContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let output = self
|
||||
.commands()
|
||||
.into_iter()
|
||||
.map(ReturnSuccess::value)
|
||||
.collect::<VecDeque<_>>();
|
||||
Ok(output.into())
|
||||
}
|
||||
|
||||
fn cd(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let path = match args.nth(0) {
|
||||
None => "/".to_string(),
|
||||
Some(v) => {
|
||||
let target = v.as_path()?;
|
||||
|
||||
let mut cwd = PathBuf::from(&self.path);
|
||||
|
||||
if target == PathBuf::from("..") {
|
||||
cwd.pop();
|
||||
} else {
|
||||
match target.to_str() {
|
||||
Some(target) => match target.chars().nth(0) {
|
||||
Some(x) if x == '/' => cwd = PathBuf::from(target),
|
||||
_ => cwd.push(target),
|
||||
},
|
||||
None => 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 cp(&self, _args: CopyArgs, _name: Tag, _path: &str) -> Result<OutputStream, ShellError> {
|
||||
Ok(OutputStream::empty())
|
||||
}
|
||||
|
||||
fn mv(&self, _args: MoveArgs, _name: Tag, _path: &str) -> Result<OutputStream, ShellError> {
|
||||
Ok(OutputStream::empty())
|
||||
}
|
||||
|
||||
fn mkdir(&self, _args: MkdirArgs, _name: Tag, _path: &str) -> Result<OutputStream, ShellError> {
|
||||
Ok(OutputStream::empty())
|
||||
}
|
||||
|
||||
fn rm(&self, _args: RemoveArgs, _name: Tag, _path: &str) -> Result<OutputStream, ShellError> {
|
||||
Ok(OutputStream::empty())
|
||||
}
|
||||
|
||||
fn complete(
|
||||
&self,
|
||||
line: &str,
|
||||
pos: usize,
|
||||
_ctx: &rustyline::Context<'_>,
|
||||
) -> Result<(usize, Vec<rustyline::completion::Pair>), rustyline::error::ReadlineError> {
|
||||
let mut completions = vec![];
|
||||
|
||||
let mut possible_completion = vec![];
|
||||
let commands = self.commands();
|
||||
for cmd in commands {
|
||||
match cmd {
|
||||
Value { value, .. } => {
|
||||
for desc in value.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(rustyline::completion::Pair {
|
||||
display: command.to_string(),
|
||||
replacement: command.to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok((replace_pos, completions))
|
||||
}
|
||||
|
||||
fn hint(
|
||||
&self,
|
||||
_line: &str,
|
||||
_pos: usize,
|
||||
_ctx: &rustyline::Context<'_>,
|
||||
_context: ExpandContext,
|
||||
) -> Option<String> {
|
||||
None
|
||||
}
|
||||
}
|
210
crates/nu-cli/src/shell/helper.rs
Normal file
210
crates/nu-cli/src/shell/helper.rs
Normal file
@ -0,0 +1,210 @@
|
||||
use crate::context::Context;
|
||||
use ansi_term::{Color, Style};
|
||||
use log::log_enabled;
|
||||
use nu_parser::{FlatShape, PipelineShape, ShapeResult, Token, TokensIterator};
|
||||
use nu_protocol::{errln, outln};
|
||||
use nu_source::{nom_input, HasSpan, Tag, Tagged, Text};
|
||||
use rustyline::completion::Completer;
|
||||
use rustyline::error::ReadlineError;
|
||||
use rustyline::highlight::Highlighter;
|
||||
use rustyline::hint::Hinter;
|
||||
use std::borrow::Cow::{self, Owned};
|
||||
|
||||
pub(crate) struct Helper {
|
||||
context: Context,
|
||||
pub colored_prompt: String,
|
||||
}
|
||||
|
||||
impl Helper {
|
||||
pub(crate) fn new(context: Context) -> Helper {
|
||||
Helper {
|
||||
context,
|
||||
colored_prompt: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Completer for Helper {
|
||||
type Candidate = rustyline::completion::Pair;
|
||||
fn complete(
|
||||
&self,
|
||||
line: &str,
|
||||
pos: usize,
|
||||
ctx: &rustyline::Context<'_>,
|
||||
) -> Result<(usize, Vec<rustyline::completion::Pair>), ReadlineError> {
|
||||
self.context.shell_manager.complete(line, pos, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Hinter for Helper {
|
||||
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<String> {
|
||||
let text = Text::from(line);
|
||||
self.context
|
||||
.shell_manager
|
||||
.hint(line, pos, ctx, self.context.expand_context(&text))
|
||||
}
|
||||
}
|
||||
|
||||
impl Highlighter for Helper {
|
||||
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
|
||||
&'s self,
|
||||
prompt: &'p str,
|
||||
default: bool,
|
||||
) -> Cow<'b, str> {
|
||||
use std::borrow::Cow::Borrowed;
|
||||
|
||||
if default {
|
||||
Borrowed(&self.colored_prompt)
|
||||
} else {
|
||||
Borrowed(prompt)
|
||||
}
|
||||
}
|
||||
|
||||
fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
|
||||
Owned("\x1b[1m".to_owned() + hint + "\x1b[m")
|
||||
}
|
||||
|
||||
fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
|
||||
let tokens = nu_parser::pipeline(nom_input(line));
|
||||
|
||||
match tokens {
|
||||
Err(_) => Cow::Borrowed(line),
|
||||
Ok((_rest, v)) => {
|
||||
let pipeline = match v.as_pipeline() {
|
||||
Err(_) => return Cow::Borrowed(line),
|
||||
Ok(v) => v,
|
||||
};
|
||||
|
||||
let text = Text::from(line);
|
||||
let expand_context = self.context.expand_context(&text);
|
||||
|
||||
let tokens = vec![Token::Pipeline(pipeline).into_spanned(v.span())];
|
||||
let mut tokens = TokensIterator::new(&tokens[..], expand_context, v.span());
|
||||
|
||||
let shapes = {
|
||||
// We just constructed a token list that only contains a pipeline, so it can't fail
|
||||
let result = tokens.expand_infallible(PipelineShape);
|
||||
|
||||
if let Some(failure) = result.failed {
|
||||
errln!(
|
||||
"BUG: PipelineShape didn't find a pipeline :: {:#?}",
|
||||
failure
|
||||
);
|
||||
}
|
||||
|
||||
tokens.finish_tracer();
|
||||
|
||||
tokens.state().shapes()
|
||||
};
|
||||
|
||||
if log_enabled!(target: "nu::expand_syntax", log::Level::Debug) {
|
||||
outln!("");
|
||||
let _ =
|
||||
ptree::print_tree(&tokens.expand_tracer().clone().print(Text::from(line)));
|
||||
outln!("");
|
||||
}
|
||||
|
||||
let mut painter = Painter::new();
|
||||
|
||||
for shape in shapes {
|
||||
painter.paint_shape(&shape, line);
|
||||
}
|
||||
|
||||
Cow::Owned(painter.into_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn highlight_char(&self, _line: &str, _pos: usize) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn vec_tag<T>(input: Vec<Tagged<T>>) -> Option<Tag> {
|
||||
let mut iter = input.iter();
|
||||
let first = iter.next()?.tag.clone();
|
||||
let last = iter.last();
|
||||
|
||||
Some(match last {
|
||||
None => first,
|
||||
Some(last) => first.until(&last.tag),
|
||||
})
|
||||
}
|
||||
|
||||
struct Painter {
|
||||
current: Style,
|
||||
buffer: String,
|
||||
}
|
||||
|
||||
impl Painter {
|
||||
fn new() -> Painter {
|
||||
Painter {
|
||||
current: Style::default(),
|
||||
buffer: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_string(self) -> String {
|
||||
self.buffer
|
||||
}
|
||||
|
||||
fn paint_shape(&mut self, shape: &ShapeResult, line: &str) {
|
||||
let style = match &shape {
|
||||
ShapeResult::Success(shape) => match shape.item {
|
||||
FlatShape::OpenDelimiter(_) => Color::White.normal(),
|
||||
FlatShape::CloseDelimiter(_) => Color::White.normal(),
|
||||
FlatShape::ItVariable | FlatShape::Keyword => Color::Purple.bold(),
|
||||
FlatShape::Variable | FlatShape::Identifier => Color::Purple.normal(),
|
||||
FlatShape::Type => Color::Blue.bold(),
|
||||
FlatShape::CompareOperator => Color::Yellow.normal(),
|
||||
FlatShape::DotDot => Color::Yellow.bold(),
|
||||
FlatShape::Dot => Style::new().fg(Color::White),
|
||||
FlatShape::InternalCommand => Color::Cyan.bold(),
|
||||
FlatShape::ExternalCommand => Color::Cyan.normal(),
|
||||
FlatShape::ExternalWord => Color::Green.bold(),
|
||||
FlatShape::BareMember => Color::Yellow.bold(),
|
||||
FlatShape::StringMember => Color::Yellow.bold(),
|
||||
FlatShape::String => Color::Green.normal(),
|
||||
FlatShape::Path => Color::Cyan.normal(),
|
||||
FlatShape::GlobPattern => Color::Cyan.bold(),
|
||||
FlatShape::Word => Color::Green.normal(),
|
||||
FlatShape::Pipe => Color::Purple.bold(),
|
||||
FlatShape::Flag => Color::Blue.bold(),
|
||||
FlatShape::ShorthandFlag => Color::Blue.bold(),
|
||||
FlatShape::Int => Color::Purple.bold(),
|
||||
FlatShape::Decimal => Color::Purple.bold(),
|
||||
FlatShape::Whitespace | FlatShape::Separator => Color::White.normal(),
|
||||
FlatShape::Comment => Color::Green.bold(),
|
||||
FlatShape::Garbage => Style::new().fg(Color::White).on(Color::Red),
|
||||
FlatShape::Size { number, unit } => {
|
||||
let number = number.slice(line);
|
||||
let unit = unit.slice(line);
|
||||
|
||||
self.paint(Color::Purple.bold(), number);
|
||||
self.paint(Color::Cyan.bold(), unit);
|
||||
return;
|
||||
}
|
||||
},
|
||||
ShapeResult::Fallback { shape, .. } => match shape.item {
|
||||
FlatShape::Whitespace | FlatShape::Separator => Color::White.normal(),
|
||||
_ => Style::new().fg(Color::White).on(Color::Red),
|
||||
},
|
||||
};
|
||||
|
||||
self.paint(style, shape.span().slice(line));
|
||||
}
|
||||
|
||||
fn paint(&mut self, style: Style, body: &str) {
|
||||
let infix = self.current.infix(style);
|
||||
self.current = style;
|
||||
self.buffer
|
||||
.push_str(&format!("{}{}", infix, style.paint(body)));
|
||||
}
|
||||
}
|
||||
|
||||
impl rustyline::Helper for Helper {}
|
||||
|
||||
// Use default validator for normal single line behaviour
|
||||
// In the future we can implement this for custom multi-line support
|
||||
impl rustyline::validate::Validator for Helper {}
|
45
crates/nu-cli/src/shell/shell.rs
Normal file
45
crates/nu-cli/src/shell/shell.rs
Normal file
@ -0,0 +1,45 @@
|
||||
use crate::commands::command::EvaluatedWholeStreamCommandArgs;
|
||||
use crate::commands::cp::CopyArgs;
|
||||
use crate::commands::ls::LsArgs;
|
||||
use crate::commands::mkdir::MkdirArgs;
|
||||
use crate::commands::mv::MoveArgs;
|
||||
use crate::commands::rm::RemoveArgs;
|
||||
use crate::prelude::*;
|
||||
use crate::stream::OutputStream;
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::ExpandContext;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub trait Shell: std::fmt::Debug {
|
||||
fn name(&self) -> String;
|
||||
fn homedir(&self) -> Option<PathBuf>;
|
||||
|
||||
fn ls(
|
||||
&self,
|
||||
args: LsArgs,
|
||||
context: &RunnablePerItemContext,
|
||||
) -> Result<OutputStream, ShellError>;
|
||||
fn cd(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError>;
|
||||
fn cp(&self, args: CopyArgs, name: Tag, path: &str) -> Result<OutputStream, ShellError>;
|
||||
fn mkdir(&self, args: MkdirArgs, name: Tag, path: &str) -> Result<OutputStream, ShellError>;
|
||||
fn mv(&self, args: MoveArgs, name: Tag, path: &str) -> Result<OutputStream, ShellError>;
|
||||
fn rm(&self, args: RemoveArgs, name: Tag, path: &str) -> Result<OutputStream, ShellError>;
|
||||
fn path(&self) -> String;
|
||||
fn pwd(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError>;
|
||||
fn set_path(&mut self, path: String);
|
||||
|
||||
fn complete(
|
||||
&self,
|
||||
line: &str,
|
||||
pos: usize,
|
||||
ctx: &rustyline::Context<'_>,
|
||||
) -> Result<(usize, Vec<rustyline::completion::Pair>), rustyline::error::ReadlineError>;
|
||||
|
||||
fn hint(
|
||||
&self,
|
||||
_line: &str,
|
||||
_pos: usize,
|
||||
_ctx: &rustyline::Context<'_>,
|
||||
_context: ExpandContext,
|
||||
) -> Option<String>;
|
||||
}
|
194
crates/nu-cli/src/shell/shell_manager.rs
Normal file
194
crates/nu-cli/src/shell/shell_manager.rs
Normal file
@ -0,0 +1,194 @@
|
||||
use crate::commands::command::{EvaluatedWholeStreamCommandArgs, RunnablePerItemContext};
|
||||
use crate::commands::cp::CopyArgs;
|
||||
use crate::commands::ls::LsArgs;
|
||||
use crate::commands::mkdir::MkdirArgs;
|
||||
use crate::commands::mv::MoveArgs;
|
||||
use crate::commands::rm::RemoveArgs;
|
||||
use crate::prelude::*;
|
||||
use crate::shell::filesystem_shell::FilesystemShell;
|
||||
use crate::shell::shell::Shell;
|
||||
use crate::stream::OutputStream;
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::ExpandContext;
|
||||
use parking_lot::Mutex;
|
||||
use std::error::Error;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ShellManager {
|
||||
pub(crate) current_shell: Arc<AtomicUsize>,
|
||||
pub(crate) shells: Arc<Mutex<Vec<Box<dyn Shell + Send>>>>,
|
||||
}
|
||||
|
||||
impl ShellManager {
|
||||
pub fn basic(commands: CommandRegistry) -> Result<ShellManager, Box<dyn Error>> {
|
||||
Ok(ShellManager {
|
||||
current_shell: Arc::new(AtomicUsize::new(0)),
|
||||
shells: Arc::new(Mutex::new(vec![Box::new(FilesystemShell::basic(
|
||||
commands,
|
||||
)?)])),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn insert_at_current(&mut self, shell: Box<dyn Shell + Send>) {
|
||||
self.shells.lock().push(shell);
|
||||
self.current_shell
|
||||
.store(self.shells.lock().len() - 1, Ordering::SeqCst);
|
||||
self.set_path(self.path());
|
||||
}
|
||||
|
||||
pub fn current_shell(&self) -> usize {
|
||||
self.current_shell.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
pub fn remove_at_current(&mut self) {
|
||||
{
|
||||
let mut shells = self.shells.lock();
|
||||
if shells.len() > 0 {
|
||||
if self.current_shell() == shells.len() - 1 {
|
||||
shells.pop();
|
||||
let new_len = shells.len();
|
||||
if new_len > 0 {
|
||||
self.current_shell.store(new_len - 1, Ordering::SeqCst);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
shells.remove(self.current_shell());
|
||||
}
|
||||
}
|
||||
}
|
||||
self.set_path(self.path())
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.shells.lock().is_empty()
|
||||
}
|
||||
|
||||
pub fn path(&self) -> String {
|
||||
self.shells.lock()[self.current_shell()].path()
|
||||
}
|
||||
|
||||
pub fn pwd(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let env = self.shells.lock();
|
||||
|
||||
env[self.current_shell()].pwd(args)
|
||||
}
|
||||
|
||||
pub fn set_path(&mut self, path: String) {
|
||||
self.shells.lock()[self.current_shell()].set_path(path)
|
||||
}
|
||||
|
||||
pub fn complete(
|
||||
&self,
|
||||
line: &str,
|
||||
pos: usize,
|
||||
ctx: &rustyline::Context<'_>,
|
||||
) -> Result<(usize, Vec<rustyline::completion::Pair>), rustyline::error::ReadlineError> {
|
||||
self.shells.lock()[self.current_shell()].complete(line, pos, ctx)
|
||||
}
|
||||
|
||||
pub fn hint(
|
||||
&self,
|
||||
line: &str,
|
||||
pos: usize,
|
||||
ctx: &rustyline::Context<'_>,
|
||||
context: ExpandContext,
|
||||
) -> Option<String> {
|
||||
self.shells.lock()[self.current_shell()].hint(line, pos, ctx, context)
|
||||
}
|
||||
|
||||
pub fn next(&mut self) {
|
||||
{
|
||||
let shell_len = self.shells.lock().len();
|
||||
if self.current_shell() == (shell_len - 1) {
|
||||
self.current_shell.store(0, Ordering::SeqCst);
|
||||
} else {
|
||||
self.current_shell
|
||||
.store(self.current_shell() + 1, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
self.set_path(self.path())
|
||||
}
|
||||
|
||||
pub fn prev(&mut self) {
|
||||
{
|
||||
let shell_len = self.shells.lock().len();
|
||||
if self.current_shell() == 0 {
|
||||
self.current_shell.store(shell_len - 1, Ordering::SeqCst);
|
||||
} else {
|
||||
self.current_shell
|
||||
.store(self.current_shell() - 1, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
self.set_path(self.path())
|
||||
}
|
||||
|
||||
pub fn homedir(&self) -> Option<PathBuf> {
|
||||
let env = self.shells.lock();
|
||||
|
||||
env[self.current_shell()].homedir()
|
||||
}
|
||||
|
||||
pub fn ls(
|
||||
&self,
|
||||
args: LsArgs,
|
||||
context: &RunnablePerItemContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let env = self.shells.lock();
|
||||
|
||||
env[self.current_shell()].ls(args, context)
|
||||
}
|
||||
|
||||
pub fn cd(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let env = self.shells.lock();
|
||||
|
||||
env[self.current_shell()].cd(args)
|
||||
}
|
||||
|
||||
pub fn cp(
|
||||
&self,
|
||||
args: CopyArgs,
|
||||
context: &RunnablePerItemContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let shells = self.shells.lock();
|
||||
|
||||
let path = shells[self.current_shell()].path();
|
||||
shells[self.current_shell()].cp(args, context.name.clone(), &path)
|
||||
}
|
||||
|
||||
pub fn rm(
|
||||
&self,
|
||||
args: RemoveArgs,
|
||||
context: &RunnablePerItemContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let shells = self.shells.lock();
|
||||
|
||||
let path = shells[self.current_shell()].path();
|
||||
shells[self.current_shell()].rm(args, context.name.clone(), &path)
|
||||
}
|
||||
|
||||
pub fn mkdir(
|
||||
&self,
|
||||
args: MkdirArgs,
|
||||
context: &RunnablePerItemContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let shells = self.shells.lock();
|
||||
|
||||
let path = shells[self.current_shell()].path();
|
||||
shells[self.current_shell()].mkdir(args, context.name.clone(), &path)
|
||||
}
|
||||
|
||||
pub fn mv(
|
||||
&self,
|
||||
args: MoveArgs,
|
||||
context: &RunnablePerItemContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let shells = self.shells.lock();
|
||||
|
||||
let path = shells[self.current_shell()].path();
|
||||
shells[self.current_shell()].mv(args, context.name.clone(), &path)
|
||||
}
|
||||
}
|
295
crates/nu-cli/src/shell/value_shell.rs
Normal file
295
crates/nu-cli/src/shell/value_shell.rs
Normal file
@ -0,0 +1,295 @@
|
||||
use crate::commands::command::EvaluatedWholeStreamCommandArgs;
|
||||
use crate::commands::cp::CopyArgs;
|
||||
use crate::commands::ls::LsArgs;
|
||||
use crate::commands::mkdir::MkdirArgs;
|
||||
use crate::commands::mv::MoveArgs;
|
||||
use crate::commands::rm::RemoveArgs;
|
||||
use crate::prelude::*;
|
||||
use crate::shell::shell::Shell;
|
||||
use crate::utils::ValueStructure;
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::ExpandContext;
|
||||
use nu_protocol::{ReturnSuccess, ShellTypeName, UntaggedValue, Value};
|
||||
use std::ffi::OsStr;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ValueShell {
|
||||
pub(crate) path: String,
|
||||
pub(crate) last_path: String,
|
||||
pub(crate) value: Value,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ValueShell {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "ValueShell @ {}", self.path)
|
||||
}
|
||||
}
|
||||
|
||||
impl ValueShell {
|
||||
pub fn new(value: Value) -> ValueShell {
|
||||
ValueShell {
|
||||
path: "/".to_string(),
|
||||
last_path: "/".to_string(),
|
||||
value,
|
||||
}
|
||||
}
|
||||
|
||||
fn members_under(&self, path: &Path) -> VecDeque<Value> {
|
||||
let mut shell_entries = VecDeque::new();
|
||||
let full_path = path.to_path_buf();
|
||||
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 => {
|
||||
let name: &str = &step.to_string_lossy().to_string();
|
||||
let value = viewed.get_data_by_key(name.spanned_unknown());
|
||||
if let Some(v) = value {
|
||||
viewed = v.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
match viewed {
|
||||
Value {
|
||||
value: UntaggedValue::Table(l),
|
||||
..
|
||||
} => {
|
||||
for item in l {
|
||||
shell_entries.push_back(item.clone());
|
||||
}
|
||||
}
|
||||
x => {
|
||||
shell_entries.push_back(x);
|
||||
}
|
||||
}
|
||||
|
||||
shell_entries
|
||||
}
|
||||
|
||||
fn members(&self) -> VecDeque<Value> {
|
||||
self.members_under(Path::new("."))
|
||||
}
|
||||
}
|
||||
|
||||
impl Shell for ValueShell {
|
||||
fn name(&self) -> String {
|
||||
let anchor_name = self.value.anchor_name();
|
||||
|
||||
match anchor_name {
|
||||
Some(x) => format!("{{{}}}", x),
|
||||
None => format!("<{}>", self.value.type_name()),
|
||||
}
|
||||
}
|
||||
|
||||
fn homedir(&self) -> Option<PathBuf> {
|
||||
Some(PathBuf::from("/"))
|
||||
}
|
||||
|
||||
fn ls(
|
||||
&self,
|
||||
LsArgs { path, .. }: LsArgs,
|
||||
context: &RunnablePerItemContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let mut full_path = PathBuf::from(self.path());
|
||||
let name_tag = context.name.clone();
|
||||
|
||||
if let Some(value) = &path {
|
||||
full_path.push(value.as_ref());
|
||||
}
|
||||
|
||||
let mut value_system = ValueStructure::new();
|
||||
value_system.walk_decorate(&self.value)?;
|
||||
|
||||
if !value_system.exists(&full_path) {
|
||||
if let Some(target) = &path {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Can not list entries inside",
|
||||
"No such path exists",
|
||||
target.tag(),
|
||||
));
|
||||
}
|
||||
|
||||
return Err(ShellError::labeled_error(
|
||||
"Can not list entries inside",
|
||||
"No such path exists",
|
||||
name_tag,
|
||||
));
|
||||
}
|
||||
|
||||
let output = self
|
||||
.members_under(full_path.as_path())
|
||||
.into_iter()
|
||||
.map(ReturnSuccess::value)
|
||||
.collect::<VecDeque<_>>();
|
||||
Ok(output.into())
|
||||
}
|
||||
|
||||
fn cd(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let destination = args.nth(0);
|
||||
|
||||
let path = match destination {
|
||||
None => "/".to_string(),
|
||||
Some(v) => {
|
||||
let target = v.as_path()?;
|
||||
|
||||
let mut cwd = PathBuf::from(&self.path);
|
||||
|
||||
if target == PathBuf::from("..") {
|
||||
cwd.pop();
|
||||
} else if target == PathBuf::from("-") {
|
||||
cwd = PathBuf::from(&self.last_path);
|
||||
} else {
|
||||
match target.to_str() {
|
||||
Some(target) => match target.chars().nth(0) {
|
||||
Some(x) if x == '/' => cwd = PathBuf::from(target),
|
||||
_ => cwd.push(target),
|
||||
},
|
||||
None => cwd.push(target),
|
||||
}
|
||||
}
|
||||
cwd.to_string_lossy().to_string()
|
||||
}
|
||||
};
|
||||
|
||||
let mut value_system = ValueStructure::new();
|
||||
value_system.walk_decorate(&self.value)?;
|
||||
|
||||
if !value_system.exists(&PathBuf::from(&path)) {
|
||||
if let Some(destination) = destination {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Can not change to path inside",
|
||||
"No such path exists",
|
||||
destination.tag(),
|
||||
));
|
||||
}
|
||||
|
||||
return Err(ShellError::labeled_error(
|
||||
"Can not change to path inside",
|
||||
"No such path exists",
|
||||
&args.call_info.name_tag,
|
||||
));
|
||||
}
|
||||
|
||||
let mut stream = VecDeque::new();
|
||||
stream.push_back(ReturnSuccess::change_cwd(path));
|
||||
Ok(stream.into())
|
||||
}
|
||||
|
||||
fn cp(&self, _args: CopyArgs, name: Tag, _path: &str) -> Result<OutputStream, ShellError> {
|
||||
Err(ShellError::labeled_error(
|
||||
"cp not currently supported on values",
|
||||
"not currently supported",
|
||||
name,
|
||||
))
|
||||
}
|
||||
|
||||
fn mv(&self, _args: MoveArgs, name: Tag, _path: &str) -> Result<OutputStream, ShellError> {
|
||||
Err(ShellError::labeled_error(
|
||||
"mv not currently supported on values",
|
||||
"not currently supported",
|
||||
name,
|
||||
))
|
||||
}
|
||||
|
||||
fn mkdir(&self, _args: MkdirArgs, name: Tag, _path: &str) -> Result<OutputStream, ShellError> {
|
||||
Err(ShellError::labeled_error(
|
||||
"mkdir not currently supported on values",
|
||||
"not currently supported",
|
||||
name,
|
||||
))
|
||||
}
|
||||
|
||||
fn rm(&self, _args: RemoveArgs, name: Tag, _path: &str) -> Result<OutputStream, ShellError> {
|
||||
Err(ShellError::labeled_error(
|
||||
"rm not currently supported on values",
|
||||
"not currently supported",
|
||||
name,
|
||||
))
|
||||
}
|
||||
|
||||
fn path(&self) -> String {
|
||||
self.path.clone()
|
||||
}
|
||||
|
||||
fn pwd(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let mut stream = VecDeque::new();
|
||||
stream.push_back(ReturnSuccess::value(
|
||||
UntaggedValue::string(self.path()).into_value(&args.call_info.name_tag),
|
||||
));
|
||||
Ok(stream.into())
|
||||
}
|
||||
|
||||
fn set_path(&mut self, path: String) {
|
||||
self.last_path = self.path.clone();
|
||||
self.path = path;
|
||||
}
|
||||
|
||||
fn complete(
|
||||
&self,
|
||||
line: &str,
|
||||
pos: usize,
|
||||
_ctx: &rustyline::Context<'_>,
|
||||
) -> Result<(usize, Vec<rustyline::completion::Pair>), rustyline::error::ReadlineError> {
|
||||
let mut completions = vec![];
|
||||
|
||||
let mut possible_completion = vec![];
|
||||
let members = self.members();
|
||||
for member in members {
|
||||
match member {
|
||||
Value { value, .. } => {
|
||||
for desc in value.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(rustyline::completion::Pair {
|
||||
display: command.to_string(),
|
||||
replacement: command.to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok((replace_pos, completions))
|
||||
}
|
||||
|
||||
fn hint(
|
||||
&self,
|
||||
_line: &str,
|
||||
_pos: usize,
|
||||
_ctx: &rustyline::Context<'_>,
|
||||
_context: ExpandContext,
|
||||
) -> Option<String> {
|
||||
None
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user