forked from extern/nushell
Merge pull request #90 from nushell/path_completion
Add path completions
This commit is contained in:
commit
58b0e571d3
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -487,6 +487,7 @@ dependencies = [
|
||||
"nu-ansi-term",
|
||||
"nu-engine",
|
||||
"nu-parser",
|
||||
"nu-path",
|
||||
"nu-protocol",
|
||||
"reedline",
|
||||
"thiserror",
|
||||
|
@ -5,8 +5,10 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine" }
|
||||
nu-path = { path = "../nu-path" }
|
||||
nu-parser = { path = "../nu-parser" }
|
||||
nu-protocol = { path = "../nu-protocol" }
|
||||
|
||||
miette = { version = "3.0.0", features = ["fancy"] }
|
||||
thiserror = "1.0.29"
|
||||
nu-ansi-term = "0.36.0"
|
||||
|
@ -8,6 +8,8 @@ use nu_protocol::{
|
||||
};
|
||||
use reedline::Completer;
|
||||
|
||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
||||
|
||||
pub struct NuCompleter {
|
||||
engine_state: Rc<RefCell<EngineState>>,
|
||||
}
|
||||
@ -28,6 +30,8 @@ impl Completer for NuCompleter {
|
||||
|
||||
let flattened = flatten_block(&working_set, &output);
|
||||
|
||||
// println!("flattened: {:?}", flattened);
|
||||
|
||||
for flat in flattened {
|
||||
if pos >= flat.0.start && pos <= flat.0.end {
|
||||
match &flat.1 {
|
||||
@ -80,6 +84,25 @@ impl Completer for NuCompleter {
|
||||
})
|
||||
.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![]
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
@ -81,6 +81,14 @@ impl Highlighter for NuHighlighter {
|
||||
Style::new().fg(nu_ansi_term::Color::Yellow).bold(),
|
||||
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((
|
||||
Style::new().fg(nu_ansi_term::Color::Blue).bold(),
|
||||
next_token,
|
||||
|
@ -15,7 +15,7 @@ impl Command for Cd {
|
||||
}
|
||||
|
||||
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(
|
||||
|
@ -31,7 +31,14 @@ impl Command for Ls {
|
||||
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
|
||||
let pattern = if let Some(expr) = call.positional.get(0) {
|
||||
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 {
|
||||
"*".into()
|
||||
};
|
||||
|
@ -240,6 +240,14 @@ pub fn eval_expression(
|
||||
val: s.clone(),
|
||||
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::Garbage => Ok(Value::Nothing { span: expr.span }),
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ pub enum FlatShape {
|
||||
Operator,
|
||||
Signature,
|
||||
String,
|
||||
Filepath,
|
||||
GlobPattern,
|
||||
Variable,
|
||||
Custom(String),
|
||||
}
|
||||
@ -118,7 +120,12 @@ pub fn flatten_expression(
|
||||
Expr::Bool(_) => {
|
||||
vec![(expr.span, FlatShape::Bool)]
|
||||
}
|
||||
|
||||
Expr::Filepath(_) => {
|
||||
vec![(expr.span, FlatShape::Filepath)]
|
||||
}
|
||||
Expr::GlobPattern(_) => {
|
||||
vec![(expr.span, FlatShape::GlobPattern)]
|
||||
}
|
||||
Expr::List(list) => {
|
||||
let mut output = vec![];
|
||||
for l in list {
|
||||
|
@ -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(
|
||||
working_set: &mut StateWorkingSet,
|
||||
span: Span,
|
||||
@ -1364,7 +1426,7 @@ pub fn parse_shape_name(
|
||||
b"number" => SyntaxShape::Number,
|
||||
b"range" => SyntaxShape::Range,
|
||||
b"int" => SyntaxShape::Int,
|
||||
b"path" => SyntaxShape::FilePath,
|
||||
b"path" => SyntaxShape::Filepath,
|
||||
b"glob" => SyntaxShape::GlobPattern,
|
||||
b"block" => SyntaxShape::Block(None), //FIXME
|
||||
b"cond" => SyntaxShape::RowCondition,
|
||||
@ -2320,9 +2382,9 @@ pub fn parse_value(
|
||||
SyntaxShape::Number => parse_number(bytes, span),
|
||||
SyntaxShape::Int => parse_int(bytes, span),
|
||||
SyntaxShape::Range => parse_range(working_set, span),
|
||||
SyntaxShape::String | SyntaxShape::GlobPattern | SyntaxShape::FilePath => {
|
||||
parse_string(working_set, span)
|
||||
}
|
||||
SyntaxShape::Filepath => parse_filepath(working_set, span),
|
||||
SyntaxShape::GlobPattern => parse_glob_pattern(working_set, span),
|
||||
SyntaxShape::String => parse_string(working_set, span),
|
||||
SyntaxShape::Block(_) => {
|
||||
if bytes.starts_with(b"{") {
|
||||
parse_block_expression(working_set, shape, span)
|
||||
|
@ -23,6 +23,8 @@ pub enum Expr {
|
||||
List(Vec<Expression>),
|
||||
Table(Vec<Expression>, Vec<Vec<Expression>>),
|
||||
Keyword(Vec<u8>, Span, Box<Expression>),
|
||||
Filepath(String),
|
||||
GlobPattern(String),
|
||||
String(String), // FIXME: improve this in the future?
|
||||
CellPath(CellPath),
|
||||
FullCellPath(Box<FullCellPath>),
|
||||
|
@ -28,7 +28,7 @@ pub enum SyntaxShape {
|
||||
Int,
|
||||
|
||||
/// A filepath is allowed
|
||||
FilePath,
|
||||
Filepath,
|
||||
|
||||
/// A glob pattern is allowed, eg `foo*`
|
||||
GlobPattern,
|
||||
@ -86,7 +86,7 @@ impl SyntaxShape {
|
||||
SyntaxShape::Custom(custom, _) => custom.to_type(),
|
||||
SyntaxShape::Duration => Type::Duration,
|
||||
SyntaxShape::Expression => Type::Unknown,
|
||||
SyntaxShape::FilePath => Type::String,
|
||||
SyntaxShape::Filepath => Type::String,
|
||||
SyntaxShape::Filesize => Type::Filesize,
|
||||
SyntaxShape::FullCellPath => Type::Unknown,
|
||||
SyntaxShape::GlobPattern => Type::String,
|
||||
|
@ -12,7 +12,6 @@ pub enum Type {
|
||||
Block,
|
||||
CellPath,
|
||||
Duration,
|
||||
FilePath,
|
||||
Filesize,
|
||||
List(Box<Type>),
|
||||
Number,
|
||||
@ -32,7 +31,6 @@ impl Display for Type {
|
||||
Type::Bool => write!(f, "bool"),
|
||||
Type::CellPath => write!(f, "cell path"),
|
||||
Type::Duration => write!(f, "duration"),
|
||||
Type::FilePath => write!(f, "filepath"),
|
||||
Type::Filesize => write!(f, "filesize"),
|
||||
Type::Float => write!(f, "float"),
|
||||
Type::Int => write!(f, "int"),
|
||||
|
Loading…
Reference in New Issue
Block a user