Update rustyline to latest (#2565)

* Update rustyline to latest

* Go ahead and use rustyline for testing
This commit is contained in:
Jonathan Turner 2020-09-17 18:02:30 +12:00 committed by GitHub
parent 1dc8f3300e
commit 8453261211
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 372 additions and 313 deletions

View file

@ -71,7 +71,7 @@ steps:
- bash: RUSTFLAGS="-D warnings" cargo clippy --all -- -D clippy::unwrap_used
condition: eq(variables['style'], 'canary')
displayName: Check clippy lints
- bash: RUSTFLAGS="-D warnings" cargo test --all --no-default-features
- bash: RUSTFLAGS="-D warnings" cargo test --all --no-default-features --features=rustyline-support
condition: eq(variables['style'], 'minimal')
displayName: Run tests
- bash: RUSTFLAGS="-D warnings" cargo test --all --features=extra

24
Cargo.lock generated
View file

@ -987,7 +987,7 @@ version = "3.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0b676fa23f995faf587496dcd1c80fead847ed58d2da52ac1caca9a72790dd2"
dependencies = [
"nix",
"nix 0.17.0",
"winapi 0.3.9",
]
@ -1834,7 +1834,7 @@ dependencies = [
"lazy_static 1.4.0",
"libc",
"mach",
"nix",
"nix 0.17.0",
"pin-utils",
"uom 0.28.0",
"winapi 0.3.9",
@ -1921,7 +1921,7 @@ dependencies = [
"heim-runtime",
"libc",
"macaddr",
"nix",
"nix 0.17.0",
]
[[package]]
@ -2796,6 +2796,18 @@ dependencies = [
"void",
]
[[package]]
name = "nix"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83450fe6a6142ddd95fb064b746083fc4ef1705fe81f64a64e1d4b39f54a1055"
dependencies = [
"bitflags",
"cc",
"cfg-if",
"libc",
]
[[package]]
name = "nodrop"
version = "0.1.14"
@ -4503,16 +4515,16 @@ dependencies = [
[[package]]
name = "rustyline"
version = "6.2.0"
version = "6.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3358c21cbbc1a751892528db4e1de4b7a2b6a73f001e215aaba97d712cfa9777"
checksum = "6f0d5e7b0219a3eadd5439498525d4765c59b7c993ef0c12244865cd2d988413"
dependencies = [
"cfg-if",
"dirs-next",
"libc",
"log 0.4.11",
"memchr",
"nix",
"nix 0.18.0",
"scopeguard",
"unicode-segmentation",
"unicode-width",

View file

@ -75,6 +75,7 @@ default = [
"ptree-support",
"term-support",
"uuid-support",
"rustyline-support",
"match",
"post",
"fetch",
@ -105,6 +106,7 @@ ctrlc-support = ["nu-cli/ctrlc"]
directories-support = ["nu-cli/directories", "nu-cli/dirs", "nu-data/directories", "nu-data/dirs"]
git-support = ["nu-cli/git2"]
ptree-support = ["nu-cli/ptree"]
rustyline-support = ["nu-cli/rustyline-support"]
term-support = ["nu-cli/term"]
trash-support = ["nu-cli/trash-support"]
uuid-support = ["nu-cli/uuid_crate"]

View file

@ -24,51 +24,51 @@ ansi_term = "0.12.1"
async-recursion = "0.3.1"
async-trait = "0.1.40"
base64 = "0.12.3"
bigdecimal = { version = "0.1.2", features = ["serde"] }
bigdecimal = {version = "0.1.2", features = ["serde"]}
byte-unit = "4.0.9"
bytes = "0.5.6"
calamine = "0.16.1"
chrono = { version = "0.4.15", features = ["serde"] }
chrono = {version = "0.4.15", features = ["serde"]}
clap = "2.33.3"
codespan-reporting = "0.9.5"
csv = "1.1.3"
ctrlc = { version = "3.1.6", optional = true }
ctrlc = {version = "3.1.6", optional = true}
derive-new = "0.5.8"
directories = { version = "3.0.1", optional = true }
dirs = { version = "3.0.1", optional = true }
directories = {version = "3.0.1", optional = true}
dirs = {version = "3.0.1", optional = true}
dtparse = "1.1.0"
dunce = "1.0.1"
eml-parser = "0.1.0"
filesize = "0.2.0"
fs_extra = "1.2.0"
futures = { version = "0.3.5", features = ["compat", "io-compat"] }
futures = {version = "0.3.5", features = ["compat", "io-compat"]}
futures-util = "0.3.5"
futures_codec = "0.4.1"
getset = "0.1.1"
git2 = { version = "0.13.11", default_features = false, optional = true }
git2 = {version = "0.13.11", default_features = false, optional = true}
glob = "0.3.0"
htmlescape = "0.3.1"
ical = "0.6.0"
ichwh = { version = "0.3.4", optional = true }
indexmap = { version = "1.6.0", features = ["serde-1"] }
ichwh = {version = "0.3.4", optional = true}
indexmap = {version = "1.6.0", features = ["serde-1"]}
itertools = "0.9.0"
log = "0.4.11"
meval = "0.2.0"
natural = "0.5.0"
num-bigint = { version = "0.2.6", features = ["serde"] }
num-format = { version = "0.4.0", features = ["with-num-bigint"] }
num-bigint = {version = "0.2.6", features = ["serde"]}
num-format = {version = "0.4.0", features = ["with-num-bigint"]}
num-traits = "0.2.12"
parking_lot = "0.11.0"
pin-utils = "0.1.0"
pretty-hex = "0.2.0"
ptree = { version = "0.3.0", optional = true }
ptree = {version = "0.3.0", optional = true}
query_interface = "0.3.5"
rand = "0.7.3"
regex = "1.3.9"
roxmltree = "0.13.0"
rust-embed = "5.6.0"
rustyline = "6.2.0"
serde = { version = "1.0.115", features = ["derive"] }
rustyline = {version = "6.3.0", optional = true}
serde = {version = "1.0.115", features = ["derive"]}
serde-hjson = "0.9.1"
serde_bytes = "0.11.5"
serde_ini = "0.2.0"
@ -79,26 +79,26 @@ sha2 = "0.9.1"
shellexpand = "2.0.0"
strip-ansi-escapes = "0.1.0"
tempfile = "3.1.0"
term = { version = "0.6.1", optional = true }
term = {version = "0.6.1", optional = true}
term_size = "0.3.2"
termcolor = "1.1.0"
toml = "0.5.6"
unicode-segmentation = "1.6.0"
uuid_crate = { package = "uuid", version = "0.8.1", features = ["v4"], optional = true }
which = { version = "4.0.2", optional = true }
zip = { version = "0.5.7", optional = true }
uuid_crate = {package = "uuid", version = "0.8.1", features = ["v4"], optional = true}
which = {version = "4.0.2", optional = true}
zip = {version = "0.5.7", optional = true}
clipboard = { version = "0.5.0", optional = true }
Inflector = "0.11"
clipboard = {version = "0.5.0", optional = true}
encoding_rs = "0.8.24"
quick-xml = "0.18.1"
rayon = "1.4.0"
trash = { version = "1.1.1", optional = true }
trash = {version = "1.1.1", optional = true}
url = "2.1.1"
Inflector = "0.11"
[target.'cfg(unix)'.dependencies]
users = "0.10.0"
umask = "1.0.0"
users = "0.10.0"
# TODO this will be possible with new dependency resolver
# (currently on nightly behind -Zfeatures=itarget):
@ -112,8 +112,7 @@ optional = true
version = "0.23.1"
[build-dependencies]
git2 = { version = "0.13.11", optional = true }
git2 = {version = "0.13.11", optional = true}
[dev-dependencies]
quickcheck = "0.9.2"
@ -121,5 +120,6 @@ quickcheck_macros = "0.9.1"
[features]
clipboard-cli = ["clipboard"]
rustyline-support = ["rustyline"]
stable = []
trash-support = ["trash"]

View file

@ -1,9 +1,9 @@
use crate::commands::classified::block::run_block;
use crate::commands::classified::maybe_text_codec::{MaybeTextCodec, StringOrBinary};
use crate::context::Context;
use crate::git::current_branch;
use crate::path::canonicalize;
use crate::prelude::*;
#[cfg(feature = "rustyline-support")]
use crate::shell::Helper;
use crate::EnvironmentSyncer;
use futures_codec::FramedRead;
@ -12,9 +12,14 @@ use nu_protocol::hir::{ClassifiedCommand, Expression, InternalCommand, Literal,
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value};
use log::{debug, trace};
use rustyline::config::{ColorMode, CompletionType, Config};
use rustyline::error::ReadlineError;
use rustyline::{self, config::Configurer, At, Cmd, Editor, KeyPress, Movement, Word};
#[cfg(feature = "rustyline-support")]
use rustyline::{
self,
config::Configurer,
config::{ColorMode, CompletionType, Config},
error::ReadlineError,
At, Cmd, Editor, KeyPress, Movement, Word,
};
use std::error::Error;
use std::iter::Iterator;
use std::path::{Path, PathBuf};
@ -305,7 +310,21 @@ pub async fn run_vec_of_pipelines(
Ok(())
}
#[cfg(feature = "rustyline-support")]
fn convert_rustyline_result_to_string(input: Result<String, ReadlineError>) -> LineResult {
match input {
Ok(s) => LineResult::Success(s),
Err(ReadlineError::Interrupted) => LineResult::CtrlC,
Err(ReadlineError::Eof) => LineResult::Break,
Err(err) => {
outln!("Error: {:?}", err);
LineResult::Break
}
}
}
/// The entry point for the CLI. Will register all known internal commands, load experimental commands, load plugins, then prepare the prompt and line reader for input.
#[cfg(feature = "rustyline-support")]
pub async fn cli(mut context: Context) -> Result<(), Box<dyn Error>> {
let mut syncer = EnvironmentSyncer::new();
let configuration = syncer.get_config();
@ -417,6 +436,7 @@ pub async fn cli(mut context: Context) -> Result<(), Box<dyn Error>> {
}
}
} else {
use crate::git::current_branch;
format!(
"\x1b[32m{}{}\x1b[m> ",
cwd,
@ -444,7 +464,10 @@ pub async fn cli(mut context: Context) -> Result<(), Box<dyn Error>> {
initial_command = None;
}
let line = process_line(readline, &mut context, false, true).await;
let line = match convert_rustyline_result_to_string(readline) {
LineResult::Success(s) => process_line(&s, &mut context, false, true).await,
x => x,
};
// Check the config to see if we need to update the path
// TODO: make sure config is cached so we don't path this load every call
@ -579,7 +602,7 @@ pub async fn run_pipeline_standalone(
context: &mut Context,
exit_on_error: bool,
) -> Result<(), Box<dyn Error>> {
let line = process_line(Ok(pipeline), context, redirect_stdin, false).await;
let line = process_line(&pipeline, context, redirect_stdin, false).await;
match line {
LineResult::Success(line) => {
@ -617,6 +640,7 @@ pub async fn run_pipeline_standalone(
Ok(())
}
#[cfg(feature = "rustyline-support")]
fn default_rustyline_editor_configuration() -> Editor<Helper> {
#[cfg(windows)]
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
@ -657,6 +681,7 @@ fn default_rustyline_editor_configuration() -> Editor<Helper> {
rl
}
#[cfg(feature = "rustyline-support")]
fn configure_rustyline_editor(
rl: &mut Editor<Helper>,
config: &dyn nu_data::config::Conf,
@ -771,6 +796,7 @@ fn configure_rustyline_editor(
Ok(())
}
#[cfg(feature = "rustyline-support")]
fn nu_line_editor_helper(
context: &mut Context,
config: &dyn nu_data::config::Conf,
@ -779,6 +805,7 @@ fn nu_line_editor_helper(
crate::shell::Helper::new(context.clone(), hinter)
}
#[cfg(feature = "rustyline-support")]
fn rustyline_hinter(config: &dyn nu_data::config::Conf) -> Option<rustyline::hint::HistoryHinter> {
if let Some(line_editor_vars) = config.var("line_editor") {
for (idx, value) in line_editor_vars.row_entries() {
@ -839,207 +866,199 @@ pub async fn parse_and_eval(line: &str, ctx: &mut Context) -> Result<String, She
/// Process the line by parsing the text to turn it into commands, classify those commands so that we understand what is being called in the pipeline, and then run this pipeline
pub async fn process_line(
readline: Result<String, ReadlineError>,
line: &str,
ctx: &mut Context,
redirect_stdin: bool,
cli_mode: bool,
) -> LineResult {
match &readline {
Ok(line) if line.trim() == "" => LineResult::Success(line.clone()),
if line.trim() == "" {
LineResult::Success(line.to_string())
} else {
let line = chomp_newline(line);
ctx.raw_input = line.to_string();
Ok(line) => {
let line = chomp_newline(line);
ctx.raw_input = line.to_string();
let result = match nu_parser::lite_parse(&line, 0) {
Err(err) => {
return LineResult::Error(line.to_string(), err.into());
}
Ok(val) => val,
};
debug!("=== Parsed ===");
debug!("{:#?}", result);
let mut classified_block = nu_parser::classify_block(&result, ctx.registry());
debug!("{:#?}", classified_block);
//println!("{:#?}", pipeline);
if let Some(failure) = classified_block.failed {
return LineResult::Error(line.to_string(), failure.into());
let result = match nu_parser::lite_parse(&line, 0) {
Err(err) => {
return LineResult::Error(line.to_string(), err.into());
}
// There's a special case to check before we process the pipeline:
// If we're giving a path by itself
// ...and it's not a command in the path
// ...and it doesn't have any arguments
// ...and we're in the CLI
// ...then change to this directory
if cli_mode
&& classified_block.block.block.len() == 1
&& classified_block.block.block[0].list.len() == 1
Ok(val) => val,
};
debug!("=== Parsed ===");
debug!("{:#?}", result);
let mut classified_block = nu_parser::classify_block(&result, ctx.registry());
debug!("{:#?}", classified_block);
//println!("{:#?}", pipeline);
if let Some(failure) = classified_block.failed {
return LineResult::Error(line.to_string(), failure.into());
}
// There's a special case to check before we process the pipeline:
// If we're giving a path by itself
// ...and it's not a command in the path
// ...and it doesn't have any arguments
// ...and we're in the CLI
// ...then change to this directory
if cli_mode
&& classified_block.block.block.len() == 1
&& classified_block.block.block[0].list.len() == 1
{
if let ClassifiedCommand::Internal(InternalCommand {
ref name, ref args, ..
}) = classified_block.block.block[0].list[0]
{
if let ClassifiedCommand::Internal(InternalCommand {
ref name, ref args, ..
}) = classified_block.block.block[0].list[0]
{
let internal_name = name;
let name = args
let internal_name = name;
let name = args
.positional
.as_ref()
.and_then(|potionals| {
potionals.get(0).map(|e| {
if let Expression::Literal(Literal::String(ref s)) = e.expr {
&s
} else {
""
}
})
})
.unwrap_or("");
if internal_name == "run_external"
&& args
.positional
.as_ref()
.and_then(|potionals| {
potionals.get(0).map(|e| {
if let Expression::Literal(Literal::String(ref s)) = e.expr {
&s
} else {
""
}
})
})
.unwrap_or("");
if internal_name == "run_external"
&& args
.positional
.as_ref()
.map(|ref v| v.len() == 1)
.unwrap_or(true)
&& args
.named
.as_ref()
.map(NamedArguments::is_empty)
.unwrap_or(true)
&& canonicalize(ctx.shell_manager.path(), name).is_ok()
&& Path::new(&name).is_dir()
&& !crate::commands::classified::external::did_find_command(&name)
.map(|ref v| v.len() == 1)
.unwrap_or(true)
&& args
.named
.as_ref()
.map(NamedArguments::is_empty)
.unwrap_or(true)
&& canonicalize(ctx.shell_manager.path(), name).is_ok()
&& Path::new(&name).is_dir()
&& !crate::commands::classified::external::did_find_command(&name)
{
// Here we work differently if we're in Windows because of the expected Windows behavior
#[cfg(windows)]
{
// Here we work differently if we're in Windows because of the expected Windows behavior
#[cfg(windows)]
{
if name.ends_with(':') {
// This looks like a drive shortcut. We need to a) switch drives and b) go back to the previous directory we were viewing on that drive
// But first, we need to save where we are now
let current_path = ctx.shell_manager.path();
if name.ends_with(':') {
// This looks like a drive shortcut. We need to a) switch drives and b) go back to the previous directory we were viewing on that drive
// But first, we need to save where we are now
let current_path = ctx.shell_manager.path();
let split_path: Vec<_> = current_path.split(':').collect();
if split_path.len() > 1 {
ctx.windows_drives_previous_cwd
.lock()
.insert(split_path[0].to_string(), current_path);
}
let split_path: Vec<_> = current_path.split(':').collect();
if split_path.len() > 1 {
ctx.windows_drives_previous_cwd
.lock()
.insert(split_path[0].to_string(), current_path);
}
let name = name.to_uppercase();
let new_drive: Vec<_> = name.split(':').collect();
let name = name.to_uppercase();
let new_drive: Vec<_> = name.split(':').collect();
if let Some(val) =
ctx.windows_drives_previous_cwd.lock().get(new_drive[0])
{
ctx.shell_manager.set_path(val.to_string());
return LineResult::Success(line.to_string());
} else {
ctx.shell_manager
.set_path(format!("{}\\", name.to_string()));
return LineResult::Success(line.to_string());
}
if let Some(val) =
ctx.windows_drives_previous_cwd.lock().get(new_drive[0])
{
ctx.shell_manager.set_path(val.to_string());
return LineResult::Success(line.to_string());
} else {
ctx.shell_manager.set_path(name.to_string());
ctx.shell_manager
.set_path(format!("{}\\", name.to_string()));
return LineResult::Success(line.to_string());
}
}
#[cfg(not(windows))]
{
} else {
ctx.shell_manager.set_path(name.to_string());
return LineResult::Success(line.to_string());
}
}
}
}
let input_stream = if redirect_stdin {
let file = futures::io::AllowStdIo::new(std::io::stdin());
let stream = FramedRead::new(file, MaybeTextCodec::default()).map(|line| {
if let Ok(line) = line {
let primitive = match line {
StringOrBinary::String(s) => Primitive::String(s),
StringOrBinary::Binary(b) => Primitive::Binary(b.into_iter().collect()),
};
Ok(Value {
value: UntaggedValue::Primitive(primitive),
tag: Tag::unknown(),
})
} else {
panic!("Internal error: could not read lines of text from stdin")
}
});
stream.to_input_stream()
} else {
InputStream::empty()
};
classified_block.block.expand_it_usage();
trace!("{:#?}", classified_block);
let env = ctx.get_env();
match run_block(
&classified_block.block,
ctx,
input_stream,
&Value::nothing(),
&IndexMap::new(),
&env,
)
.await
{
Ok(input) => {
// Running a pipeline gives us back a stream that we can then
// work through. At the top level, we just want to pull on the
// values to compute them.
use futures::stream::TryStreamExt;
let context = RunnableContext {
input,
shell_manager: ctx.shell_manager.clone(),
host: ctx.host.clone(),
ctrl_c: ctx.ctrl_c.clone(),
current_errors: ctx.current_errors.clone(),
registry: ctx.registry.clone(),
name: Tag::unknown(),
raw_input: line.to_string(),
};
if let Ok(mut output_stream) =
crate::commands::autoview::command::autoview(context).await
#[cfg(not(windows))]
{
loop {
match output_stream.try_next().await {
Ok(Some(ReturnSuccess::Value(Value {
value: UntaggedValue::Error(e),
..
}))) => return LineResult::Error(line.to_string(), e),
Ok(Some(_item)) => {
if ctx.ctrl_c.load(Ordering::SeqCst) {
break;
}
}
Ok(None) => break,
Err(e) => return LineResult::Error(line.to_string(), e),
}
}
ctx.shell_manager.set_path(name.to_string());
return LineResult::Success(line.to_string());
}
LineResult::Success(line.to_string())
}
Err(err) => LineResult::Error(line.to_string(), err),
}
}
Err(ReadlineError::Interrupted) => LineResult::CtrlC,
Err(ReadlineError::Eof) => LineResult::Break,
Err(err) => {
outln!("Error: {:?}", err);
LineResult::Break
let input_stream = if redirect_stdin {
let file = futures::io::AllowStdIo::new(std::io::stdin());
let stream = FramedRead::new(file, MaybeTextCodec::default()).map(|line| {
if let Ok(line) = line {
let primitive = match line {
StringOrBinary::String(s) => Primitive::String(s),
StringOrBinary::Binary(b) => Primitive::Binary(b.into_iter().collect()),
};
Ok(Value {
value: UntaggedValue::Primitive(primitive),
tag: Tag::unknown(),
})
} else {
panic!("Internal error: could not read lines of text from stdin")
}
});
stream.to_input_stream()
} else {
InputStream::empty()
};
classified_block.block.expand_it_usage();
trace!("{:#?}", classified_block);
let env = ctx.get_env();
match run_block(
&classified_block.block,
ctx,
input_stream,
&Value::nothing(),
&IndexMap::new(),
&env,
)
.await
{
Ok(input) => {
// Running a pipeline gives us back a stream that we can then
// work through. At the top level, we just want to pull on the
// values to compute them.
use futures::stream::TryStreamExt;
let context = RunnableContext {
input,
shell_manager: ctx.shell_manager.clone(),
host: ctx.host.clone(),
ctrl_c: ctx.ctrl_c.clone(),
current_errors: ctx.current_errors.clone(),
registry: ctx.registry.clone(),
name: Tag::unknown(),
raw_input: line.to_string(),
};
if let Ok(mut output_stream) =
crate::commands::autoview::command::autoview(context).await
{
loop {
match output_stream.try_next().await {
Ok(Some(ReturnSuccess::Value(Value {
value: UntaggedValue::Error(e),
..
}))) => return LineResult::Error(line.to_string(), e),
Ok(Some(_item)) => {
if ctx.ctrl_c.load(Ordering::SeqCst) {
break;
}
}
Ok(None) => break,
Err(e) => return LineResult::Error(line.to_string(), e),
}
}
}
LineResult::Success(line.to_string())
}
Err(err) => LineResult::Error(line.to_string(), err),
}
}
}

View file

@ -213,7 +213,7 @@ pub fn get_documentation(
long_desc.push_str(&format!("\n > {}\n", example.example));
} else {
let colored_example =
crate::shell::helper::Painter::paint_string(example.example, registry, &palette);
crate::shell::painter::Painter::paint_string(example.example, registry, &palette);
long_desc.push_str(&format!("\n > {}\n", colored_example));
}
}

View file

@ -41,11 +41,14 @@ pub fn nu(env: &IndexMap<String, String>, tag: impl Into<Tag>) -> Result<Value,
let config = nu_data::config::default_path()?;
nu_dict.insert_value("config-path", UntaggedValue::path(config).into_value(&tag));
let keybinding_path = crate::keybinding::keybinding_path()?;
nu_dict.insert_value(
"keybinding-path",
UntaggedValue::path(keybinding_path).into_value(&tag),
);
#[cfg(feature = "rustyline-support")]
{
let keybinding_path = crate::keybinding::keybinding_path()?;
nu_dict.insert_value(
"keybinding-path",
UntaggedValue::path(keybinding_path).into_value(&tag),
);
}
let config: Box<dyn nu_data::config::Conf> = Box::new(nu_data::config::NuConfig::new());
let history = crate::commands::history::history_path(&config);

View file

@ -15,6 +15,7 @@ extern crate quickcheck_macros;
mod cli;
mod commands;
#[cfg(feature = "rustyline-support")]
mod completion;
mod context;
mod deserializer;
@ -23,7 +24,9 @@ mod env;
mod evaluate;
mod format;
mod futures;
#[cfg(feature = "rustyline-support")]
mod git;
#[cfg(feature = "rustyline-support")]
mod keybinding;
mod path;
mod plugin;
@ -34,8 +37,11 @@ pub mod utils;
#[cfg(test)]
mod examples;
#[cfg(feature = "rustyline-support")]
pub use crate::cli::cli;
pub use crate::cli::{
cli, create_default_context, parse_and_eval, process_line, register_plugins,
create_default_context, parse_and_eval, process_line, register_plugins,
run_pipeline_standalone, run_vec_of_pipelines, LineResult,
};
pub use crate::commands::command::{

View file

@ -1,12 +1,16 @@
#![allow(clippy::module_inception)]
#[cfg(feature = "rustyline-support")]
pub(crate) mod completer;
pub(crate) mod filesystem_shell;
pub(crate) mod help_shell;
#[cfg(feature = "rustyline-support")]
pub(crate) mod helper;
pub(crate) mod painter;
pub(crate) mod palette;
pub(crate) mod shell;
pub(crate) mod shell_manager;
pub(crate) mod value_shell;
#[cfg(feature = "rustyline-support")]
pub(crate) use helper::Helper;

View file

@ -3,7 +3,7 @@ use crate::completion::flag::FlagCompleter;
use crate::completion::path::{PathCompleter, PathSuggestion};
use crate::completion::{self, Completer, Suggestion};
use crate::context;
use std::borrow::Cow;
pub(crate) struct NuCompleter {}
impl NuCompleter {}
@ -109,8 +109,8 @@ fn select_directory_suggestions(completed_paths: Vec<PathSuggestion>) -> Vec<Pat
.collect()
}
fn requote(value: String) -> String {
let value = rustyline::completion::unescape(&value, Some('\\'));
fn requote(orig_value: String) -> String {
let value: Cow<str> = rustyline::completion::unescape(&orig_value, Some('\\'));
let mut quotes = vec!['"', '\'', '`'];
let mut should_quote = false;

View file

@ -1,14 +1,13 @@
use std::borrow::Cow::{self, Owned};
use ansi_term::{Color, Style};
use nu_parser::SignatureRegistry;
use nu_protocol::hir::FlatShape;
use nu_source::{Spanned, Tag, Tagged};
use nu_source::{Tag, Tagged};
use crate::completion;
use crate::context::Context;
use crate::shell::completer::NuCompleter;
use crate::shell::palette::{DefaultPalette, Palette};
use crate::shell::painter::Painter;
use crate::shell::palette::DefaultPalette;
pub struct Helper {
completer: NuCompleter,
@ -143,90 +142,4 @@ fn vec_tag<T>(input: Vec<Tagged<T>>) -> Option<Tag> {
})
}
pub struct Painter {
original: Vec<u8>,
styles: Vec<Style>,
}
impl Painter {
fn new(original: &str) -> Painter {
let bytes: Vec<u8> = original.bytes().collect();
let bytes_count = bytes.len();
Painter {
original: bytes,
styles: vec![Color::White.normal(); bytes_count],
}
}
pub fn paint_string<'l, P: Palette>(
line: &'l str,
registry: &dyn SignatureRegistry,
palette: &P,
) -> Cow<'l, str> {
let lite_block = nu_parser::lite_parse(line, 0);
match lite_block {
Err(_) => Cow::Borrowed(line),
Ok(lb) => {
let classified = nu_parser::classify_block(&lb, registry);
let shapes = nu_parser::shapes(&classified.block);
let mut painter = Painter::new(line);
for shape in shapes {
painter.paint_shape(&shape, palette);
}
Cow::Owned(painter.into_string())
}
}
}
fn paint_shape<P: Palette>(&mut self, shape: &Spanned<FlatShape>, palette: &P) {
palette
.styles_for_shape(shape)
.iter()
.for_each(|x| self.paint(x));
}
fn paint(&mut self, styled_span: &Spanned<Style>) {
for pos in styled_span.span.start()..styled_span.span.end() {
self.styles[pos] = styled_span.item;
}
}
fn into_string(self) -> String {
let mut idx_start = 0;
let mut idx_end = 1;
if self.original.is_empty() {
String::new()
} else {
let mut builder = String::new();
let mut current_style = self.styles[0];
while idx_end < self.styles.len() {
if self.styles[idx_end] != current_style {
// Emit, as we changed styles
let intermediate = String::from_utf8_lossy(&self.original[idx_start..idx_end]);
builder.push_str(&format!("{}", current_style.paint(intermediate)));
current_style = self.styles[idx_end];
idx_start = idx_end;
idx_end += 1;
} else {
idx_end += 1;
}
}
let intermediate = String::from_utf8_lossy(&self.original[idx_start..idx_end]);
builder.push_str(&format!("{}", current_style.paint(intermediate)));
builder
}
}
}
impl rustyline::Helper for Helper {}

View file

@ -0,0 +1,92 @@
use crate::shell::palette::Palette;
use ansi_term::{Color, Style};
use nu_parser::SignatureRegistry;
use nu_protocol::hir::FlatShape;
use nu_source::Spanned;
use std::borrow::Cow;
pub struct Painter {
original: Vec<u8>,
styles: Vec<Style>,
}
impl Painter {
fn new(original: &str) -> Painter {
let bytes: Vec<u8> = original.bytes().collect();
let bytes_count = bytes.len();
Painter {
original: bytes,
styles: vec![Color::White.normal(); bytes_count],
}
}
pub fn paint_string<'l, P: Palette>(
line: &'l str,
registry: &dyn SignatureRegistry,
palette: &P,
) -> Cow<'l, str> {
let lite_block = nu_parser::lite_parse(line, 0);
match lite_block {
Err(_) => Cow::Borrowed(line),
Ok(lb) => {
let classified = nu_parser::classify_block(&lb, registry);
let shapes = nu_parser::shapes(&classified.block);
let mut painter = Painter::new(line);
for shape in shapes {
painter.paint_shape(&shape, palette);
}
Cow::Owned(painter.into_string())
}
}
}
fn paint_shape<P: Palette>(&mut self, shape: &Spanned<FlatShape>, palette: &P) {
palette
.styles_for_shape(shape)
.iter()
.for_each(|x| self.paint(x));
}
fn paint(&mut self, styled_span: &Spanned<Style>) {
for pos in styled_span.span.start()..styled_span.span.end() {
self.styles[pos] = styled_span.item;
}
}
fn into_string(self) -> String {
let mut idx_start = 0;
let mut idx_end = 1;
if self.original.is_empty() {
String::new()
} else {
let mut builder = String::new();
let mut current_style = self.styles[0];
while idx_end < self.styles.len() {
if self.styles[idx_end] != current_style {
// Emit, as we changed styles
let intermediate = String::from_utf8_lossy(&self.original[idx_start..idx_end]);
builder.push_str(&format!("{}", current_style.paint(intermediate)));
current_style = self.styles[idx_end];
idx_start = idx_end;
idx_end += 1;
} else {
idx_end += 1;
}
}
let intermediate = String::from_utf8_lossy(&self.original[idx_start..idx_end]);
builder.push_str(&format!("{}", current_style.paint(intermediate)));
builder
}
}
}

View file

@ -166,7 +166,15 @@ fn main() -> Result<(), Box<dyn Error>> {
let _ = nu_cli::register_plugins(&mut context);
}
futures::executor::block_on(nu_cli::cli(context))?;
#[cfg(feature = "rustyline-support")]
{
futures::executor::block_on(nu_cli::cli(context))?;
}
#[cfg(not(feature = "rustyline-support"))]
{
println!("Nushell needs the 'rustyline-support' feature for CLI support");
}
}
}