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-ansi-term",
|
||||||
"nu-engine",
|
"nu-engine",
|
||||||
"nu-parser",
|
"nu-parser",
|
||||||
|
"nu-path",
|
||||||
"nu-protocol",
|
"nu-protocol",
|
||||||
"reedline",
|
"reedline",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
@ -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"
|
||||||
|
@ -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())
|
||||||
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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(
|
||||||
|
@ -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()
|
||||||
};
|
};
|
||||||
|
@ -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 }),
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
|
@ -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>),
|
||||||
|
@ -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,
|
||||||
|
@ -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"),
|
||||||
|
Loading…
Reference in New Issue
Block a user