Merge pull request #90 from nushell/path_completion

Add path completions
This commit is contained in:
JT 2021-10-05 08:28:47 +13:00 committed by GitHub
commit 58b0e571d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 182 additions and 11 deletions

1
Cargo.lock generated
View File

@ -487,6 +487,7 @@ dependencies = [
"nu-ansi-term", "nu-ansi-term",
"nu-engine", "nu-engine",
"nu-parser", "nu-parser",
"nu-path",
"nu-protocol", "nu-protocol",
"reedline", "reedline",
"thiserror", "thiserror",

View File

@ -5,8 +5,10 @@ edition = "2018"
[dependencies] [dependencies]
nu-engine = { path = "../nu-engine" } nu-engine = { path = "../nu-engine" }
nu-path = { path = "../nu-path" }
nu-parser = { path = "../nu-parser" } nu-parser = { path = "../nu-parser" }
nu-protocol = { path = "../nu-protocol" } nu-protocol = { path = "../nu-protocol" }
miette = { version = "3.0.0", features = ["fancy"] } miette = { version = "3.0.0", features = ["fancy"] }
thiserror = "1.0.29" thiserror = "1.0.29"
nu-ansi-term = "0.36.0" nu-ansi-term = "0.36.0"

View File

@ -8,6 +8,8 @@ use nu_protocol::{
}; };
use reedline::Completer; use reedline::Completer;
const SEP: char = std::path::MAIN_SEPARATOR;
pub struct NuCompleter { pub struct NuCompleter {
engine_state: Rc<RefCell<EngineState>>, engine_state: Rc<RefCell<EngineState>>,
} }
@ -28,6 +30,8 @@ impl Completer for NuCompleter {
let flattened = flatten_block(&working_set, &output); let flattened = flatten_block(&working_set, &output);
// println!("flattened: {:?}", flattened);
for flat in flattened { for flat in flattened {
if pos >= flat.0.start && pos <= flat.0.end { if pos >= flat.0.start && pos <= flat.0.end {
match &flat.1 { match &flat.1 {
@ -80,6 +84,25 @@ impl Completer for NuCompleter {
}) })
.collect(); .collect();
} }
nu_parser::FlatShape::Filepath | nu_parser::FlatShape::GlobPattern => {
let prefix = working_set.get_span_contents(flat.0);
let prefix = String::from_utf8_lossy(prefix).to_string();
let results = file_path_completion(flat.0, &prefix);
return results
.into_iter()
.map(move |x| {
(
reedline::Span {
start: x.0.start - offset,
end: x.0.end - offset,
},
x.1,
)
})
.collect();
}
_ => {} _ => {}
} }
} }
@ -88,3 +111,56 @@ impl Completer for NuCompleter {
vec![] vec![]
} }
} }
fn file_path_completion(
span: nu_protocol::Span,
partial: &str,
) -> Vec<(nu_protocol::Span, String)> {
use std::path::{is_separator, Path};
let (base_dir_name, partial) = {
// If partial is only a word we want to search in the current dir
let (base, rest) = partial.rsplit_once(is_separator).unwrap_or((".", partial));
// On windows, this standardizes paths to use \
let mut base = base.replace(is_separator, &SEP.to_string());
// rsplit_once removes the separator
base.push(SEP);
(base, rest)
};
let base_dir = nu_path::expand_path(&base_dir_name);
// This check is here as base_dir.read_dir() with base_dir == "" will open the current dir
// which we don't want in this case (if we did, base_dir would already be ".")
if base_dir == Path::new("") {
return Vec::new();
}
if let Ok(result) = base_dir.read_dir() {
result
.filter_map(|entry| {
entry.ok().and_then(|entry| {
let mut file_name = entry.file_name().to_string_lossy().into_owned();
if matches(partial, &file_name) {
let mut path = format!("{}{}", base_dir_name, file_name);
if entry.path().is_dir() {
path.push(SEP);
file_name.push(SEP);
}
Some((span, path))
} else {
None
}
})
})
.collect()
} else {
Vec::new()
}
}
fn matches(partial: &str, from: &str) -> bool {
from.to_ascii_lowercase()
.starts_with(&partial.to_ascii_lowercase())
}

View File

@ -81,6 +81,14 @@ impl Highlighter for NuHighlighter {
Style::new().fg(nu_ansi_term::Color::Yellow).bold(), Style::new().fg(nu_ansi_term::Color::Yellow).bold(),
next_token, next_token,
)), )),
FlatShape::Filepath => output.push((
Style::new().fg(nu_ansi_term::Color::Yellow).bold(),
next_token,
)),
FlatShape::GlobPattern => output.push((
Style::new().fg(nu_ansi_term::Color::Yellow).bold(),
next_token,
)),
FlatShape::Variable => output.push(( FlatShape::Variable => output.push((
Style::new().fg(nu_ansi_term::Color::Blue).bold(), Style::new().fg(nu_ansi_term::Color::Blue).bold(),
next_token, next_token,

View File

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

View File

@ -31,7 +31,14 @@ impl Command for Ls {
) -> Result<nu_protocol::Value, nu_protocol::ShellError> { ) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
let pattern = if let Some(expr) = call.positional.get(0) { let pattern = if let Some(expr) = call.positional.get(0) {
let result = eval_expression(context, expr)?; let result = eval_expression(context, expr)?;
result.as_string()? let mut result = result.as_string()?;
let path = std::path::Path::new(&result);
if path.is_dir() {
result.push('*');
}
result
} else { } else {
"*".into() "*".into()
}; };

View File

@ -240,6 +240,14 @@ pub fn eval_expression(
val: s.clone(), val: s.clone(),
span: expr.span, span: expr.span,
}), }),
Expr::Filepath(s) => Ok(Value::String {
val: s.clone(),
span: expr.span,
}),
Expr::GlobPattern(s) => Ok(Value::String {
val: s.clone(),
span: expr.span,
}),
Expr::Signature(_) => Ok(Value::Nothing { span: expr.span }), Expr::Signature(_) => Ok(Value::Nothing { span: expr.span }),
Expr::Garbage => Ok(Value::Nothing { span: expr.span }), Expr::Garbage => Ok(Value::Nothing { span: expr.span }),
} }

View File

@ -15,6 +15,8 @@ pub enum FlatShape {
Operator, Operator,
Signature, Signature,
String, String,
Filepath,
GlobPattern,
Variable, Variable,
Custom(String), Custom(String),
} }
@ -118,7 +120,12 @@ pub fn flatten_expression(
Expr::Bool(_) => { Expr::Bool(_) => {
vec![(expr.span, FlatShape::Bool)] vec![(expr.span, FlatShape::Bool)]
} }
Expr::Filepath(_) => {
vec![(expr.span, FlatShape::Filepath)]
}
Expr::GlobPattern(_) => {
vec![(expr.span, FlatShape::GlobPattern)]
}
Expr::List(list) => { Expr::List(list) => {
let mut output = vec![]; let mut output = vec![];
for l in list { for l in list {

View File

@ -1320,6 +1320,68 @@ pub fn parse_full_cell_path(
} }
} }
pub fn parse_filepath(
working_set: &mut StateWorkingSet,
span: Span,
) -> (Expression, Option<ParseError>) {
let bytes = working_set.get_span_contents(span);
let bytes = if (bytes.starts_with(b"\"") && bytes.ends_with(b"\"") && bytes.len() > 1)
|| (bytes.starts_with(b"\'") && bytes.ends_with(b"\'") && bytes.len() > 1)
{
&bytes[1..(bytes.len() - 1)]
} else {
bytes
};
if let Ok(token) = String::from_utf8(bytes.into()) {
(
Expression {
expr: Expr::Filepath(token),
span,
ty: Type::String,
custom_completion: None,
},
None,
)
} else {
(
garbage(span),
Some(ParseError::Expected("string".into(), span)),
)
}
}
pub fn parse_glob_pattern(
working_set: &mut StateWorkingSet,
span: Span,
) -> (Expression, Option<ParseError>) {
let bytes = working_set.get_span_contents(span);
let bytes = if (bytes.starts_with(b"\"") && bytes.ends_with(b"\"") && bytes.len() > 1)
|| (bytes.starts_with(b"\'") && bytes.ends_with(b"\'") && bytes.len() > 1)
{
&bytes[1..(bytes.len() - 1)]
} else {
bytes
};
if let Ok(token) = String::from_utf8(bytes.into()) {
(
Expression {
expr: Expr::GlobPattern(token),
span,
ty: Type::String,
custom_completion: None,
},
None,
)
} else {
(
garbage(span),
Some(ParseError::Expected("string".into(), span)),
)
}
}
pub fn parse_string( pub fn parse_string(
working_set: &mut StateWorkingSet, working_set: &mut StateWorkingSet,
span: Span, span: Span,
@ -1364,7 +1426,7 @@ pub fn parse_shape_name(
b"number" => SyntaxShape::Number, b"number" => SyntaxShape::Number,
b"range" => SyntaxShape::Range, b"range" => SyntaxShape::Range,
b"int" => SyntaxShape::Int, b"int" => SyntaxShape::Int,
b"path" => SyntaxShape::FilePath, b"path" => SyntaxShape::Filepath,
b"glob" => SyntaxShape::GlobPattern, b"glob" => SyntaxShape::GlobPattern,
b"block" => SyntaxShape::Block(None), //FIXME b"block" => SyntaxShape::Block(None), //FIXME
b"cond" => SyntaxShape::RowCondition, b"cond" => SyntaxShape::RowCondition,
@ -2320,9 +2382,9 @@ pub fn parse_value(
SyntaxShape::Number => parse_number(bytes, span), SyntaxShape::Number => parse_number(bytes, span),
SyntaxShape::Int => parse_int(bytes, span), SyntaxShape::Int => parse_int(bytes, span),
SyntaxShape::Range => parse_range(working_set, span), SyntaxShape::Range => parse_range(working_set, span),
SyntaxShape::String | SyntaxShape::GlobPattern | SyntaxShape::FilePath => { SyntaxShape::Filepath => parse_filepath(working_set, span),
parse_string(working_set, span) SyntaxShape::GlobPattern => parse_glob_pattern(working_set, span),
} SyntaxShape::String => parse_string(working_set, span),
SyntaxShape::Block(_) => { SyntaxShape::Block(_) => {
if bytes.starts_with(b"{") { if bytes.starts_with(b"{") {
parse_block_expression(working_set, shape, span) parse_block_expression(working_set, shape, span)

View File

@ -23,6 +23,8 @@ pub enum Expr {
List(Vec<Expression>), List(Vec<Expression>),
Table(Vec<Expression>, Vec<Vec<Expression>>), Table(Vec<Expression>, Vec<Vec<Expression>>),
Keyword(Vec<u8>, Span, Box<Expression>), Keyword(Vec<u8>, Span, Box<Expression>),
Filepath(String),
GlobPattern(String),
String(String), // FIXME: improve this in the future? String(String), // FIXME: improve this in the future?
CellPath(CellPath), CellPath(CellPath),
FullCellPath(Box<FullCellPath>), FullCellPath(Box<FullCellPath>),

View File

@ -28,7 +28,7 @@ pub enum SyntaxShape {
Int, Int,
/// A filepath is allowed /// A filepath is allowed
FilePath, Filepath,
/// A glob pattern is allowed, eg `foo*` /// A glob pattern is allowed, eg `foo*`
GlobPattern, GlobPattern,
@ -86,7 +86,7 @@ impl SyntaxShape {
SyntaxShape::Custom(custom, _) => custom.to_type(), SyntaxShape::Custom(custom, _) => custom.to_type(),
SyntaxShape::Duration => Type::Duration, SyntaxShape::Duration => Type::Duration,
SyntaxShape::Expression => Type::Unknown, SyntaxShape::Expression => Type::Unknown,
SyntaxShape::FilePath => Type::String, SyntaxShape::Filepath => Type::String,
SyntaxShape::Filesize => Type::Filesize, SyntaxShape::Filesize => Type::Filesize,
SyntaxShape::FullCellPath => Type::Unknown, SyntaxShape::FullCellPath => Type::Unknown,
SyntaxShape::GlobPattern => Type::String, SyntaxShape::GlobPattern => Type::String,

View File

@ -12,7 +12,6 @@ pub enum Type {
Block, Block,
CellPath, CellPath,
Duration, Duration,
FilePath,
Filesize, Filesize,
List(Box<Type>), List(Box<Type>),
Number, Number,
@ -32,7 +31,6 @@ impl Display for Type {
Type::Bool => write!(f, "bool"), Type::Bool => write!(f, "bool"),
Type::CellPath => write!(f, "cell path"), Type::CellPath => write!(f, "cell path"),
Type::Duration => write!(f, "duration"), Type::Duration => write!(f, "duration"),
Type::FilePath => write!(f, "filepath"),
Type::Filesize => write!(f, "filesize"), Type::Filesize => write!(f, "filesize"),
Type::Float => write!(f, "float"), Type::Float => write!(f, "float"),
Type::Int => write!(f, "int"), Type::Int => write!(f, "int"),