mirror of
https://github.com/nushell/nushell.git
synced 2024-11-07 17:14:23 +01:00
nu-cli: directory syntax shape + completions (#5299)
This commit is contained in:
parent
661283c4d2
commit
5ff2ae628b
@ -1,6 +1,6 @@
|
||||
use crate::completions::{
|
||||
CommandCompletion, Completer, CustomCompletion, DotNuCompletion, FileCompletion,
|
||||
FlagCompletion, VariableCompletion,
|
||||
CommandCompletion, Completer, CustomCompletion, DirectoryCompletion, DotNuCompletion,
|
||||
FileCompletion, FlagCompletion, VariableCompletion,
|
||||
};
|
||||
use nu_parser::{flatten_expression, parse, FlatShape};
|
||||
use nu_protocol::{
|
||||
@ -153,6 +153,19 @@ impl NuCompleter {
|
||||
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 => {
|
||||
let mut completer = FileCompletion::new(self.engine_state.clone());
|
||||
|
||||
|
101
crates/nu-cli/src/completions/directory_completions.rs
Normal file
101
crates/nu-cli/src/completions/directory_completions.rs
Normal 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
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ mod command_completions;
|
||||
mod completer;
|
||||
mod completion_options;
|
||||
mod custom_completions;
|
||||
mod directory_completions;
|
||||
mod dotnu_completions;
|
||||
mod file_completions;
|
||||
mod flag_completions;
|
||||
@ -13,6 +14,7 @@ pub use command_completions::CommandCompletion;
|
||||
pub use completer::NuCompleter;
|
||||
pub use completion_options::{CompletionOptions, SortBy};
|
||||
pub use custom_completions::CustomCompletion;
|
||||
pub use directory_completions::DirectoryCompletion;
|
||||
pub use dotnu_completions::DotNuCompletion;
|
||||
pub use file_completions::{file_path_completion, partial_from, FileCompletion};
|
||||
pub use flag_completions::FlagCompletion;
|
||||
|
@ -178,6 +178,11 @@ impl Highlighter for NuHighlighter {
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
)),
|
||||
FlatShape::Directory => output.push((
|
||||
// nushell Directory
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
)),
|
||||
FlatShape::GlobPattern => output.push((
|
||||
// nushell GlobPattern
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
|
@ -18,7 +18,7 @@ fn file_completions() {
|
||||
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 target_dir = format!("cp {}", dir_str);
|
||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
// Create the expected values
|
||||
@ -45,8 +45,34 @@ fn file_completions() {
|
||||
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
|
||||
fn new_engine() -> (PathBuf, String, EngineState) {
|
||||
pub fn new_engine() -> (PathBuf, String, EngineState) {
|
||||
// Target folder inside assets
|
||||
let dir = fs::fixtures().join("completions");
|
||||
let mut dir_str = dir
|
||||
@ -61,14 +87,14 @@ fn new_engine() -> (PathBuf, String, EngineState) {
|
||||
}
|
||||
|
||||
// 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| {
|
||||
assert_eq!(it.0, &it.1.value);
|
||||
});
|
||||
}
|
||||
|
||||
// append the separator to the converted path
|
||||
fn folder(path: PathBuf) -> String {
|
||||
pub fn folder(path: PathBuf) -> String {
|
||||
let mut converted_path = file(path);
|
||||
converted_path.push(SEP);
|
||||
|
||||
@ -76,6 +102,6 @@ fn folder(path: PathBuf) -> 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()
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ pub fn get_shape_color(shape: String, conf: &Config) -> Style {
|
||||
"shape_record" => Style::new().fg(Color::Cyan).bold(),
|
||||
"shape_block" => Style::new().fg(Color::Blue).bold(),
|
||||
"shape_filepath" => Style::new().fg(Color::Cyan),
|
||||
"shape_directory" => Style::new().fg(Color::Cyan),
|
||||
"shape_globpattern" => Style::new().fg(Color::Cyan).bold(),
|
||||
"shape_variable" => Style::new().fg(Color::Purple),
|
||||
"shape_flag" => Style::new().fg(Color::Blue).bold(),
|
||||
|
@ -17,7 +17,7 @@ impl Command for Cd {
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("cd")
|
||||
.optional("path", SyntaxShape::Filepath, "the path to change to")
|
||||
.optional("path", SyntaxShape::Directory, "the path to change to")
|
||||
.category(Category::FileSystem)
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ impl Command for Mkdir {
|
||||
Signature::build("mkdir")
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::Filepath,
|
||||
SyntaxShape::Directory,
|
||||
"the name(s) of the path(s) to create",
|
||||
)
|
||||
.switch("show-created-paths", "show the path(s) created.", Some('s'))
|
||||
|
@ -237,6 +237,7 @@ fn convert_to_value(
|
||||
expr.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::FullCellPath(full_cell_path) => {
|
||||
if !full_cell_path.tail.is_empty() {
|
||||
|
@ -530,6 +530,15 @@ pub fn eval_expression(
|
||||
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) => {
|
||||
let cwd = current_dir_str(engine_state, stack)?;
|
||||
let path = expand_path_with(s, cwd);
|
||||
|
@ -25,6 +25,7 @@ pub enum FlatShape {
|
||||
Record,
|
||||
Block,
|
||||
Filepath,
|
||||
Directory,
|
||||
DateTime,
|
||||
GlobPattern,
|
||||
Variable,
|
||||
@ -56,6 +57,7 @@ impl Display for FlatShape {
|
||||
FlatShape::Record => write!(f, "shape_record"),
|
||||
FlatShape::Block => write!(f, "shape_block"),
|
||||
FlatShape::Filepath => write!(f, "shape_filepath"),
|
||||
FlatShape::Directory => write!(f, "shape_directory"),
|
||||
FlatShape::GlobPattern => write!(f, "shape_globpattern"),
|
||||
FlatShape::Variable => write!(f, "shape_variable"),
|
||||
FlatShape::Flag => write!(f, "shape_flag"),
|
||||
@ -279,6 +281,9 @@ pub fn flatten_expression(
|
||||
Expr::Filepath(_) => {
|
||||
vec![(expr.span, FlatShape::Filepath)]
|
||||
}
|
||||
Expr::Directory(_) => {
|
||||
vec![(expr.span, FlatShape::Directory)]
|
||||
}
|
||||
Expr::GlobPattern(_) => {
|
||||
vec![(expr.span, FlatShape::GlobPattern)]
|
||||
}
|
||||
|
@ -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(
|
||||
working_set: &mut StateWorkingSet,
|
||||
span: Span,
|
||||
@ -2551,6 +2578,7 @@ pub fn parse_shape_name(
|
||||
b"cell-path" => SyntaxShape::CellPath,
|
||||
b"duration" => SyntaxShape::Duration,
|
||||
b"path" => SyntaxShape::Filepath,
|
||||
b"directory" => SyntaxShape::Directory,
|
||||
b"expr" => SyntaxShape::Expression,
|
||||
b"filesize" => SyntaxShape::Filesize,
|
||||
b"glob" => SyntaxShape::GlobPattern,
|
||||
@ -3869,6 +3897,7 @@ pub fn parse_value(
|
||||
SyntaxShape::Filesize => parse_filesize(working_set, span),
|
||||
SyntaxShape::Range => parse_range(working_set, span, expand_aliases_denylist),
|
||||
SyntaxShape::Filepath => parse_filepath(working_set, span),
|
||||
SyntaxShape::Directory => parse_directory(working_set, span),
|
||||
SyntaxShape::GlobPattern => parse_glob_pattern(working_set, span),
|
||||
SyntaxShape::String => parse_string(working_set, span),
|
||||
SyntaxShape::Binary => parse_binary(working_set, span),
|
||||
@ -4868,6 +4897,7 @@ pub fn discover_captures_in_expr(
|
||||
}
|
||||
}
|
||||
Expr::Filepath(_) => {}
|
||||
Expr::Directory(_) => {}
|
||||
Expr::Float(_) => {}
|
||||
Expr::FullCellPath(cell_path) => {
|
||||
let result = discover_captures_in_expr(working_set, &cell_path.head, seen, seen_blocks);
|
||||
|
@ -33,6 +33,7 @@ pub enum Expr {
|
||||
ValueWithUnit(Box<Expression>, Spanned<Unit>),
|
||||
DateTime(chrono::DateTime<FixedOffset>),
|
||||
Filepath(String),
|
||||
Directory(String),
|
||||
GlobPattern(String),
|
||||
String(String),
|
||||
CellPath(CellPath),
|
||||
|
@ -162,6 +162,7 @@ impl Expression {
|
||||
}
|
||||
Expr::ImportPattern(_) => false,
|
||||
Expr::Filepath(_) => false,
|
||||
Expr::Directory(_) => false,
|
||||
Expr::Float(_) => false,
|
||||
Expr::FullCellPath(full_cell_path) => {
|
||||
if full_cell_path.head.has_in_variable(working_set) {
|
||||
@ -320,6 +321,7 @@ impl Expression {
|
||||
}
|
||||
}
|
||||
Expr::Filepath(_) => {}
|
||||
Expr::Directory(_) => {}
|
||||
Expr::Float(_) => {}
|
||||
Expr::FullCellPath(full_cell_path) => {
|
||||
full_cell_path
|
||||
@ -467,6 +469,7 @@ impl Expression {
|
||||
}
|
||||
}
|
||||
Expr::Filepath(_) => {}
|
||||
Expr::Directory(_) => {}
|
||||
Expr::Float(_) => {}
|
||||
Expr::FullCellPath(full_cell_path) => {
|
||||
full_cell_path
|
||||
|
@ -34,6 +34,9 @@ pub enum SyntaxShape {
|
||||
/// A filepath is allowed
|
||||
Filepath,
|
||||
|
||||
/// A directory is allowed
|
||||
Directory,
|
||||
|
||||
/// A glob pattern is allowed, eg `foo*`
|
||||
GlobPattern,
|
||||
|
||||
@ -105,6 +108,7 @@ impl SyntaxShape {
|
||||
SyntaxShape::Duration => Type::Duration,
|
||||
SyntaxShape::Expression => Type::Any,
|
||||
SyntaxShape::Filepath => Type::String,
|
||||
SyntaxShape::Directory => Type::String,
|
||||
SyntaxShape::Filesize => Type::Filesize,
|
||||
SyntaxShape::FullCellPath => Type::Any,
|
||||
SyntaxShape::GlobPattern => Type::String,
|
||||
@ -145,6 +149,7 @@ impl Display for SyntaxShape {
|
||||
SyntaxShape::Range => write!(f, "range"),
|
||||
SyntaxShape::Int => write!(f, "int"),
|
||||
SyntaxShape::Filepath => write!(f, "path"),
|
||||
SyntaxShape::Directory => write!(f, "directory"),
|
||||
SyntaxShape::GlobPattern => write!(f, "glob"),
|
||||
SyntaxShape::ImportPattern => write!(f, "import"),
|
||||
SyntaxShape::Block(_) => write!(f, "block"),
|
||||
|
Loading…
Reference in New Issue
Block a user