forked from extern/nushell
Support completion for paths with multiple dots (#1640)
* refactor: expand_path and expand_ndots now work for any string. * refactor: refactor test and add new ones. * refactor: convert expanded to owned string * feat: pub export of expand_ndots * feat: add completion for ndots in fs-shell
This commit is contained in:
parent
818171cc2c
commit
9ec2aca86f
@ -11,6 +11,7 @@ use crate::shell::completer::NuCompleter;
|
||||
use crate::shell::shell::Shell;
|
||||
use crate::utils::FileStructure;
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::expand_ndots;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue};
|
||||
use rustyline::completion::FilenameCompleter;
|
||||
use rustyline::hint::{Hinter, HistoryHinter};
|
||||
@ -994,7 +995,27 @@ impl Shell for FilesystemShell {
|
||||
pos: usize,
|
||||
ctx: &rustyline::Context<'_>,
|
||||
) -> Result<(usize, Vec<rustyline::completion::Pair>), rustyline::error::ReadlineError> {
|
||||
self.completer.complete(line, pos, ctx)
|
||||
let expanded = expand_ndots(&line);
|
||||
|
||||
// Find the first not-matching char position, if there is one
|
||||
let differ_pos = line
|
||||
.chars()
|
||||
.zip(expanded.chars())
|
||||
.enumerate()
|
||||
.find(|(_index, (a, b))| a != b)
|
||||
.map(|(differ_pos, _)| differ_pos);
|
||||
|
||||
let pos = if let Some(differ_pos) = differ_pos {
|
||||
if differ_pos < pos {
|
||||
pos + (expanded.len() - line.len())
|
||||
} else {
|
||||
pos
|
||||
}
|
||||
} else {
|
||||
pos
|
||||
};
|
||||
|
||||
self.completer.complete(&expanded, pos, ctx)
|
||||
}
|
||||
|
||||
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<String> {
|
||||
|
@ -8,5 +8,6 @@ mod signature;
|
||||
pub use crate::files::Files;
|
||||
pub use crate::lite_parse::{lite_parse, LiteBlock};
|
||||
pub use crate::parse::{classify_block, garbage};
|
||||
pub use crate::path::expand_ndots;
|
||||
pub use crate::shapes::shapes;
|
||||
pub use crate::signature::{Signature, SignatureRegistry};
|
||||
|
@ -366,7 +366,7 @@ fn parse_arg(
|
||||
}
|
||||
SyntaxShape::Pattern => {
|
||||
let trimmed = trim_quotes(&lite_arg.item);
|
||||
let expanded = expand_path(&trimmed);
|
||||
let expanded = expand_path(&trimmed).to_string();
|
||||
(
|
||||
SpannedExpression::new(Expression::pattern(expanded), lite_arg.span),
|
||||
None,
|
||||
@ -378,7 +378,7 @@ fn parse_arg(
|
||||
SyntaxShape::Unit => parse_unit(&lite_arg),
|
||||
SyntaxShape::Path => {
|
||||
let trimmed = trim_quotes(&lite_arg.item);
|
||||
let expanded = expand_path(&trimmed);
|
||||
let expanded = expand_path(&trimmed).to_string();
|
||||
let path = Path::new(&expanded);
|
||||
(
|
||||
SpannedExpression::new(Expression::FilePath(path.to_path_buf()), lite_arg.span),
|
||||
@ -1068,7 +1068,7 @@ fn classify_pipeline(
|
||||
} else {
|
||||
let name = lite_cmd.name.clone().map(|v| {
|
||||
let trimmed = trim_quotes(&v);
|
||||
expand_path(&trimmed)
|
||||
expand_path(&trimmed).to_string()
|
||||
});
|
||||
|
||||
let mut args = vec![];
|
||||
|
@ -1,35 +1,69 @@
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
use std::borrow::Cow;
|
||||
|
||||
fn expand_ndots(path: &str) -> String {
|
||||
let path = Path::new(path);
|
||||
let mut expanded = PathBuf::new();
|
||||
|
||||
for component in path.components() {
|
||||
match component {
|
||||
Component::Normal(normal) => {
|
||||
if let Some(normal) = normal.to_str() {
|
||||
if normal.chars().all(|c| c == '.') {
|
||||
for _ in 0..(normal.len() - 1) {
|
||||
expanded.push("..");
|
||||
fn handle_dots_push(string: &mut String, count: u8) {
|
||||
if count < 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
if count == 1 {
|
||||
string.push('.');
|
||||
return;
|
||||
}
|
||||
|
||||
for _ in 0..(count - 1) {
|
||||
string.push_str("../");
|
||||
}
|
||||
|
||||
string.pop(); // remove last '/'
|
||||
}
|
||||
|
||||
pub fn expand_ndots(path: &str) -> Cow<'_, str> {
|
||||
let mut dots_count = 0u8;
|
||||
let ndots_present = {
|
||||
for chr in path.chars() {
|
||||
if chr == '.' {
|
||||
dots_count += 1;
|
||||
} else {
|
||||
expanded.push(normal);
|
||||
if dots_count > 2 {
|
||||
break;
|
||||
}
|
||||
|
||||
dots_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
dots_count > 2
|
||||
};
|
||||
|
||||
if !ndots_present {
|
||||
return path.into();
|
||||
}
|
||||
|
||||
let mut dots_count = 0u8;
|
||||
let mut expanded = String::new();
|
||||
for chr in path.chars() {
|
||||
if chr != '.' {
|
||||
handle_dots_push(&mut expanded, dots_count);
|
||||
dots_count = 0;
|
||||
expanded.push(chr);
|
||||
} else {
|
||||
expanded.push(normal);
|
||||
dots_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
c => expanded.push(c.as_os_str()),
|
||||
}
|
||||
handle_dots_push(&mut expanded, dots_count);
|
||||
|
||||
expanded.into()
|
||||
}
|
||||
|
||||
expanded.to_string_lossy().to_string()
|
||||
}
|
||||
pub fn expand_path<'a>(path: &'a str) -> Cow<'a, str> {
|
||||
let tilde_expansion: Cow<'a, str> = shellexpand::tilde(path);
|
||||
let ndots_expansion: Cow<'a, str> = match tilde_expansion {
|
||||
Cow::Borrowed(b) => expand_ndots(b),
|
||||
Cow::Owned(o) => expand_ndots(&o).to_string().into(),
|
||||
};
|
||||
|
||||
pub fn expand_path(path: &str) -> String {
|
||||
let tilde_expansion = shellexpand::tilde(path);
|
||||
expand_ndots(&tilde_expansion)
|
||||
ndots_expansion
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -37,16 +71,25 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn expand_in_relative_path() {
|
||||
let expected = Path::new("../..");
|
||||
let expanded = PathBuf::from(expand_path("..."));
|
||||
assert_eq!(expected, &expanded);
|
||||
fn string_without_ndots() {
|
||||
assert_eq!("../hola", &expand_ndots("../hola").to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expand_in_absolute_path() {
|
||||
let expected = Path::new("/foo/../..");
|
||||
let expanded = PathBuf::from(expand_path("/foo/..."));
|
||||
assert_eq!(expected, &expanded);
|
||||
fn string_with_three_ndots() {
|
||||
assert_eq!("../..", &expand_ndots("...").to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_with_three_ndots_and_final_slash() {
|
||||
assert_eq!("../../", &expand_ndots(".../").to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_with_three_ndots_and_garbage() {
|
||||
assert_eq!(
|
||||
"ls ../../ garbage.*[",
|
||||
&expand_ndots("ls .../ garbage.*[").to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user