nu-cli: directory syntax shape + completions (#5299)

This commit is contained in:
Herlon Aguiar 2022-04-22 22:18:51 +02:00 committed by GitHub
parent 661283c4d2
commit 5ff2ae628b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 211 additions and 9 deletions

View File

@ -1,6 +1,6 @@
use crate::completions::{ use crate::completions::{
CommandCompletion, Completer, CustomCompletion, DotNuCompletion, FileCompletion, CommandCompletion, Completer, CustomCompletion, DirectoryCompletion, DotNuCompletion,
FlagCompletion, VariableCompletion, FileCompletion, FlagCompletion, VariableCompletion,
}; };
use nu_parser::{flatten_expression, parse, FlatShape}; use nu_parser::{flatten_expression, parse, FlatShape};
use nu_protocol::{ use nu_protocol::{
@ -153,6 +153,19 @@ impl NuCompleter {
pos, pos,
); );
} }
FlatShape::Directory => {
let mut completer =
DirectoryCompletion::new(self.engine_state.clone());
return self.process_completion(
&mut completer,
&working_set,
prefix,
new_span,
offset,
pos,
);
}
FlatShape::Filepath | FlatShape::GlobPattern => { FlatShape::Filepath | FlatShape::GlobPattern => {
let mut completer = FileCompletion::new(self.engine_state.clone()); let mut completer = FileCompletion::new(self.engine_state.clone());

View File

@ -0,0 +1,101 @@
use crate::completions::{file_path_completion, Completer};
use nu_protocol::{
engine::{EngineState, StateWorkingSet},
levenshtein_distance, Span,
};
use reedline::Suggestion;
use std::path::Path;
use std::sync::Arc;
const SEP: char = std::path::MAIN_SEPARATOR;
#[derive(Clone)]
pub struct DirectoryCompletion {
engine_state: Arc<EngineState>,
}
impl DirectoryCompletion {
pub fn new(engine_state: Arc<EngineState>) -> Self {
Self { engine_state }
}
}
impl Completer for DirectoryCompletion {
fn fetch(
&mut self,
_: &StateWorkingSet,
prefix: Vec<u8>,
span: Span,
offset: usize,
_: usize,
) -> Vec<Suggestion> {
let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD") {
match d.as_string() {
Ok(s) => s,
Err(_) => "".to_string(),
}
} else {
"".to_string()
};
let prefix = String::from_utf8_lossy(&prefix).to_string();
// Filter only the folders
let output: Vec<_> = file_path_completion(span, &prefix, &cwd)
.into_iter()
.filter_map(move |x| {
if x.1.ends_with(SEP) {
return Some(Suggestion {
value: x.1,
description: None,
extra: None,
span: reedline::Span {
start: x.0.start - offset,
end: x.0.end - offset,
},
});
}
None
})
.collect();
output
}
// Sort results prioritizing the non hidden folders
fn sort(&self, items: Vec<Suggestion>, prefix: Vec<u8>) -> Vec<Suggestion> {
let prefix_str = String::from_utf8_lossy(&prefix).to_string();
// Sort items
let mut sorted_items = items;
sorted_items.sort_by(|a, b| a.value.cmp(&b.value));
sorted_items.sort_by(|a, b| {
let a_distance = levenshtein_distance(&prefix_str, &a.value);
let b_distance = levenshtein_distance(&prefix_str, &b.value);
a_distance.cmp(&b_distance)
});
// Separate the results between hidden and non hidden
let mut hidden: Vec<Suggestion> = vec![];
let mut non_hidden: Vec<Suggestion> = vec![];
for item in sorted_items.into_iter() {
let item_path = Path::new(&item.value);
if let Some(value) = item_path.file_name() {
if let Some(value) = value.to_str() {
if value.starts_with('.') {
hidden.push(item);
} else {
non_hidden.push(item);
}
}
}
}
// Append the hidden folders to the non hidden vec to avoid creating a new vec
non_hidden.append(&mut hidden);
non_hidden
}
}

View File

@ -3,6 +3,7 @@ mod command_completions;
mod completer; mod completer;
mod completion_options; mod completion_options;
mod custom_completions; mod custom_completions;
mod directory_completions;
mod dotnu_completions; mod dotnu_completions;
mod file_completions; mod file_completions;
mod flag_completions; mod flag_completions;
@ -13,6 +14,7 @@ pub use command_completions::CommandCompletion;
pub use completer::NuCompleter; pub use completer::NuCompleter;
pub use completion_options::{CompletionOptions, SortBy}; pub use completion_options::{CompletionOptions, SortBy};
pub use custom_completions::CustomCompletion; pub use custom_completions::CustomCompletion;
pub use directory_completions::DirectoryCompletion;
pub use dotnu_completions::DotNuCompletion; pub use dotnu_completions::DotNuCompletion;
pub use file_completions::{file_path_completion, partial_from, FileCompletion}; pub use file_completions::{file_path_completion, partial_from, FileCompletion};
pub use flag_completions::FlagCompletion; pub use flag_completions::FlagCompletion;

View File

@ -178,6 +178,11 @@ impl Highlighter for NuHighlighter {
get_shape_color(shape.1.to_string(), &self.config), get_shape_color(shape.1.to_string(), &self.config),
next_token, next_token,
)), )),
FlatShape::Directory => output.push((
// nushell Directory
get_shape_color(shape.1.to_string(), &self.config),
next_token,
)),
FlatShape::GlobPattern => output.push(( FlatShape::GlobPattern => output.push((
// nushell GlobPattern // nushell GlobPattern
get_shape_color(shape.1.to_string(), &self.config), get_shape_color(shape.1.to_string(), &self.config),

View File

@ -18,7 +18,7 @@ fn file_completions() {
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack); let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
// Test completions for the current folder // Test completions for the current folder
let target_dir = format!("cd {}", dir_str); let target_dir = format!("cp {}", dir_str);
let suggestions = completer.complete(&target_dir, target_dir.len()); let suggestions = completer.complete(&target_dir, target_dir.len());
// Create the expected values // Create the expected values
@ -45,8 +45,34 @@ fn file_completions() {
match_suggestions(expected_paths, suggestions); match_suggestions(expected_paths, suggestions);
} }
#[test]
fn folder_completions() {
// Create a new engine
let (dir, dir_str, engine) = new_engine();
let stack = Stack::new();
// Instatiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
// Test completions for the current folder
let target_dir = format!("cd {}", dir_str);
let suggestions = completer.complete(&target_dir, target_dir.len());
// Create the expected values
let expected_paths: Vec<String> = vec![
folder(dir.join("test_a")),
folder(dir.join("test_b")),
folder(dir.join("another")),
folder(dir.join(".hidden_folder")),
];
// Match the results
match_suggestions(expected_paths, suggestions);
}
// creates a new engine with the current path into the completions fixtures folder // creates a new engine with the current path into the completions fixtures folder
fn new_engine() -> (PathBuf, String, EngineState) { pub fn new_engine() -> (PathBuf, String, EngineState) {
// Target folder inside assets // Target folder inside assets
let dir = fs::fixtures().join("completions"); let dir = fs::fixtures().join("completions");
let mut dir_str = dir let mut dir_str = dir
@ -61,14 +87,14 @@ fn new_engine() -> (PathBuf, String, EngineState) {
} }
// match a list of suggestions with the expected values // match a list of suggestions with the expected values
fn match_suggestions(expected: Vec<String>, suggestions: Vec<Suggestion>) { pub fn match_suggestions(expected: Vec<String>, suggestions: Vec<Suggestion>) {
expected.iter().zip(suggestions).for_each(|it| { expected.iter().zip(suggestions).for_each(|it| {
assert_eq!(it.0, &it.1.value); assert_eq!(it.0, &it.1.value);
}); });
} }
// append the separator to the converted path // append the separator to the converted path
fn folder(path: PathBuf) -> String { pub fn folder(path: PathBuf) -> String {
let mut converted_path = file(path); let mut converted_path = file(path);
converted_path.push(SEP); converted_path.push(SEP);
@ -76,6 +102,6 @@ fn folder(path: PathBuf) -> String {
} }
// convert a given path to string // convert a given path to string
fn file(path: PathBuf) -> String { pub fn file(path: PathBuf) -> String {
path.into_os_string().into_string().unwrap_or_default() path.into_os_string().into_string().unwrap_or_default()
} }

View File

@ -29,6 +29,7 @@ pub fn get_shape_color(shape: String, conf: &Config) -> Style {
"shape_record" => Style::new().fg(Color::Cyan).bold(), "shape_record" => Style::new().fg(Color::Cyan).bold(),
"shape_block" => Style::new().fg(Color::Blue).bold(), "shape_block" => Style::new().fg(Color::Blue).bold(),
"shape_filepath" => Style::new().fg(Color::Cyan), "shape_filepath" => Style::new().fg(Color::Cyan),
"shape_directory" => Style::new().fg(Color::Cyan),
"shape_globpattern" => Style::new().fg(Color::Cyan).bold(), "shape_globpattern" => Style::new().fg(Color::Cyan).bold(),
"shape_variable" => Style::new().fg(Color::Purple), "shape_variable" => Style::new().fg(Color::Purple),
"shape_flag" => Style::new().fg(Color::Blue).bold(), "shape_flag" => Style::new().fg(Color::Blue).bold(),

View File

@ -17,7 +17,7 @@ impl Command for Cd {
fn signature(&self) -> nu_protocol::Signature { fn signature(&self) -> nu_protocol::Signature {
Signature::build("cd") Signature::build("cd")
.optional("path", SyntaxShape::Filepath, "the path to change to") .optional("path", SyntaxShape::Directory, "the path to change to")
.category(Category::FileSystem) .category(Category::FileSystem)
} }

View File

@ -21,7 +21,7 @@ impl Command for Mkdir {
Signature::build("mkdir") Signature::build("mkdir")
.rest( .rest(
"rest", "rest",
SyntaxShape::Filepath, SyntaxShape::Directory,
"the name(s) of the path(s) to create", "the name(s) of the path(s) to create",
) )
.switch("show-created-paths", "show the path(s) created.", Some('s')) .switch("show-created-paths", "show the path(s) created.", Some('s'))

View File

@ -237,6 +237,7 @@ fn convert_to_value(
expr.span, expr.span,
)), )),
Expr::Filepath(val) => Ok(Value::String { val, span }), Expr::Filepath(val) => Ok(Value::String { val, span }),
Expr::Directory(val) => Ok(Value::String { val, span }),
Expr::Float(val) => Ok(Value::Float { val, span }), Expr::Float(val) => Ok(Value::Float { val, span }),
Expr::FullCellPath(full_cell_path) => { Expr::FullCellPath(full_cell_path) => {
if !full_cell_path.tail.is_empty() { if !full_cell_path.tail.is_empty() {

View File

@ -530,6 +530,15 @@ pub fn eval_expression(
span: expr.span, span: expr.span,
}) })
} }
Expr::Directory(s) => {
let cwd = current_dir_str(engine_state, stack)?;
let path = expand_path_with(s, cwd);
Ok(Value::String {
val: path.to_string_lossy().to_string(),
span: expr.span,
})
}
Expr::GlobPattern(s) => { Expr::GlobPattern(s) => {
let cwd = current_dir_str(engine_state, stack)?; let cwd = current_dir_str(engine_state, stack)?;
let path = expand_path_with(s, cwd); let path = expand_path_with(s, cwd);

View File

@ -25,6 +25,7 @@ pub enum FlatShape {
Record, Record,
Block, Block,
Filepath, Filepath,
Directory,
DateTime, DateTime,
GlobPattern, GlobPattern,
Variable, Variable,
@ -56,6 +57,7 @@ impl Display for FlatShape {
FlatShape::Record => write!(f, "shape_record"), FlatShape::Record => write!(f, "shape_record"),
FlatShape::Block => write!(f, "shape_block"), FlatShape::Block => write!(f, "shape_block"),
FlatShape::Filepath => write!(f, "shape_filepath"), FlatShape::Filepath => write!(f, "shape_filepath"),
FlatShape::Directory => write!(f, "shape_directory"),
FlatShape::GlobPattern => write!(f, "shape_globpattern"), FlatShape::GlobPattern => write!(f, "shape_globpattern"),
FlatShape::Variable => write!(f, "shape_variable"), FlatShape::Variable => write!(f, "shape_variable"),
FlatShape::Flag => write!(f, "shape_flag"), FlatShape::Flag => write!(f, "shape_flag"),
@ -279,6 +281,9 @@ pub fn flatten_expression(
Expr::Filepath(_) => { Expr::Filepath(_) => {
vec![(expr.span, FlatShape::Filepath)] vec![(expr.span, FlatShape::Filepath)]
} }
Expr::Directory(_) => {
vec![(expr.span, FlatShape::Directory)]
}
Expr::GlobPattern(_) => { Expr::GlobPattern(_) => {
vec![(expr.span, FlatShape::GlobPattern)] vec![(expr.span, FlatShape::GlobPattern)]
} }

View File

@ -1915,6 +1915,33 @@ pub fn parse_full_cell_path(
} }
} }
pub fn parse_directory(
working_set: &mut StateWorkingSet,
span: Span,
) -> (Expression, Option<ParseError>) {
let bytes = working_set.get_span_contents(span);
let bytes = trim_quotes(bytes);
trace!("parsing: directory");
if let Ok(token) = String::from_utf8(bytes.into()) {
trace!("-- found {}", token);
(
Expression {
expr: Expr::Directory(token),
span,
ty: Type::String,
custom_completion: None,
},
None,
)
} else {
(
garbage(span),
Some(ParseError::Expected("directory".into(), span)),
)
}
}
pub fn parse_filepath( pub fn parse_filepath(
working_set: &mut StateWorkingSet, working_set: &mut StateWorkingSet,
span: Span, span: Span,
@ -2551,6 +2578,7 @@ pub fn parse_shape_name(
b"cell-path" => SyntaxShape::CellPath, b"cell-path" => SyntaxShape::CellPath,
b"duration" => SyntaxShape::Duration, b"duration" => SyntaxShape::Duration,
b"path" => SyntaxShape::Filepath, b"path" => SyntaxShape::Filepath,
b"directory" => SyntaxShape::Directory,
b"expr" => SyntaxShape::Expression, b"expr" => SyntaxShape::Expression,
b"filesize" => SyntaxShape::Filesize, b"filesize" => SyntaxShape::Filesize,
b"glob" => SyntaxShape::GlobPattern, b"glob" => SyntaxShape::GlobPattern,
@ -3869,6 +3897,7 @@ pub fn parse_value(
SyntaxShape::Filesize => parse_filesize(working_set, span), SyntaxShape::Filesize => parse_filesize(working_set, span),
SyntaxShape::Range => parse_range(working_set, span, expand_aliases_denylist), SyntaxShape::Range => parse_range(working_set, span, expand_aliases_denylist),
SyntaxShape::Filepath => parse_filepath(working_set, span), SyntaxShape::Filepath => parse_filepath(working_set, span),
SyntaxShape::Directory => parse_directory(working_set, span),
SyntaxShape::GlobPattern => parse_glob_pattern(working_set, span), SyntaxShape::GlobPattern => parse_glob_pattern(working_set, span),
SyntaxShape::String => parse_string(working_set, span), SyntaxShape::String => parse_string(working_set, span),
SyntaxShape::Binary => parse_binary(working_set, span), SyntaxShape::Binary => parse_binary(working_set, span),
@ -4868,6 +4897,7 @@ pub fn discover_captures_in_expr(
} }
} }
Expr::Filepath(_) => {} Expr::Filepath(_) => {}
Expr::Directory(_) => {}
Expr::Float(_) => {} Expr::Float(_) => {}
Expr::FullCellPath(cell_path) => { Expr::FullCellPath(cell_path) => {
let result = discover_captures_in_expr(working_set, &cell_path.head, seen, seen_blocks); let result = discover_captures_in_expr(working_set, &cell_path.head, seen, seen_blocks);

View File

@ -33,6 +33,7 @@ pub enum Expr {
ValueWithUnit(Box<Expression>, Spanned<Unit>), ValueWithUnit(Box<Expression>, Spanned<Unit>),
DateTime(chrono::DateTime<FixedOffset>), DateTime(chrono::DateTime<FixedOffset>),
Filepath(String), Filepath(String),
Directory(String),
GlobPattern(String), GlobPattern(String),
String(String), String(String),
CellPath(CellPath), CellPath(CellPath),

View File

@ -162,6 +162,7 @@ impl Expression {
} }
Expr::ImportPattern(_) => false, Expr::ImportPattern(_) => false,
Expr::Filepath(_) => false, Expr::Filepath(_) => false,
Expr::Directory(_) => false,
Expr::Float(_) => false, Expr::Float(_) => false,
Expr::FullCellPath(full_cell_path) => { Expr::FullCellPath(full_cell_path) => {
if full_cell_path.head.has_in_variable(working_set) { if full_cell_path.head.has_in_variable(working_set) {
@ -320,6 +321,7 @@ impl Expression {
} }
} }
Expr::Filepath(_) => {} Expr::Filepath(_) => {}
Expr::Directory(_) => {}
Expr::Float(_) => {} Expr::Float(_) => {}
Expr::FullCellPath(full_cell_path) => { Expr::FullCellPath(full_cell_path) => {
full_cell_path full_cell_path
@ -467,6 +469,7 @@ impl Expression {
} }
} }
Expr::Filepath(_) => {} Expr::Filepath(_) => {}
Expr::Directory(_) => {}
Expr::Float(_) => {} Expr::Float(_) => {}
Expr::FullCellPath(full_cell_path) => { Expr::FullCellPath(full_cell_path) => {
full_cell_path full_cell_path

View File

@ -34,6 +34,9 @@ pub enum SyntaxShape {
/// A filepath is allowed /// A filepath is allowed
Filepath, Filepath,
/// A directory is allowed
Directory,
/// A glob pattern is allowed, eg `foo*` /// A glob pattern is allowed, eg `foo*`
GlobPattern, GlobPattern,
@ -105,6 +108,7 @@ impl SyntaxShape {
SyntaxShape::Duration => Type::Duration, SyntaxShape::Duration => Type::Duration,
SyntaxShape::Expression => Type::Any, SyntaxShape::Expression => Type::Any,
SyntaxShape::Filepath => Type::String, SyntaxShape::Filepath => Type::String,
SyntaxShape::Directory => Type::String,
SyntaxShape::Filesize => Type::Filesize, SyntaxShape::Filesize => Type::Filesize,
SyntaxShape::FullCellPath => Type::Any, SyntaxShape::FullCellPath => Type::Any,
SyntaxShape::GlobPattern => Type::String, SyntaxShape::GlobPattern => Type::String,
@ -145,6 +149,7 @@ impl Display for SyntaxShape {
SyntaxShape::Range => write!(f, "range"), SyntaxShape::Range => write!(f, "range"),
SyntaxShape::Int => write!(f, "int"), SyntaxShape::Int => write!(f, "int"),
SyntaxShape::Filepath => write!(f, "path"), SyntaxShape::Filepath => write!(f, "path"),
SyntaxShape::Directory => write!(f, "directory"),
SyntaxShape::GlobPattern => write!(f, "glob"), SyntaxShape::GlobPattern => write!(f, "glob"),
SyntaxShape::ImportPattern => write!(f, "import"), SyntaxShape::ImportPattern => write!(f, "import"),
SyntaxShape::Block(_) => write!(f, "block"), SyntaxShape::Block(_) => write!(f, "block"),