Add a custom path completer to nushell. (#2463)

Previously, we used rustyline's filename completer. This allowed us to make
progress on the completion engine without building all the parts at once. We now
need our own filename completer to make progress.

The primary driver to having our own filename completer is that it can better
integrate with our path constructs. For example, if we have

    > ls .../<TAB>

we want to show a list of suggestions that includes all files two directories up
from the current working directory. The least jarring experience to a user would
be to maintain the three dots. The easiest way for us to do this is by building
our own completer and path constructs.
This commit is contained in:
Jason Gedge 2020-08-30 22:28:09 -04:00 committed by GitHub
parent 965e07d8cc
commit 188d33b306
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 39 additions and 19 deletions

View File

@ -33,7 +33,7 @@ impl Completer {
.collect();
if partial != "" {
let path_completer = crate::completion::path::Completer::new();
let path_completer = crate::completion::path::Completer;
let path_results = path_completer.complete(ctx, partial);
suggestions.extend(path_results.into_iter().filter(|suggestion| {
let path = Path::new(&suggestion.replacement);

View File

@ -1,27 +1,47 @@
use rustyline::completion::FilenameCompleter;
use std::path::Path;
use crate::completion::{Context, Suggestion};
pub struct Completer {
inner: FilenameCompleter,
}
const SEP: char = std::path::MAIN_SEPARATOR;
pub struct Completer;
impl Completer {
pub fn new() -> Completer {
Completer {
inner: FilenameCompleter::new(),
}
}
pub fn complete(&self, _ctx: &Context<'_>, partial: &str) -> Vec<Suggestion> {
let expanded = nu_parser::expand_ndots(partial);
let expanded = expanded.as_ref();
if let Ok((_pos, pairs)) = self.inner.complete_path(&expanded, expanded.len()) {
pairs
.into_iter()
.map(|v| Suggestion {
replacement: v.replacement,
display: v.display,
let (base_dir_name, partial) = match expanded.rfind(SEP) {
Some(pos) => expanded.split_at(pos + SEP.len_utf8()),
None => ("", expanded),
};
let base_dir = if base_dir_name == "" {
Path::new(".")
} else {
Path::new(base_dir_name)
};
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 file_name.starts_with(partial) {
let mut path = format!("{}{}", base_dir_name, file_name);
if entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false) {
path.push(std::path::MAIN_SEPARATOR);
file_name.push(std::path::MAIN_SEPARATOR);
}
Some(Suggestion {
replacement: path,
display: file_name,
})
} else {
None
}
})
})
.collect()
} else {

View File

@ -41,12 +41,12 @@ impl NuCompleter {
}
LocationType::Flag(cmd) => {
let flag_completer = crate::completion::flag::Completer {};
let flag_completer = crate::completion::flag::Completer;
flag_completer.complete(context, cmd, partial)
}
LocationType::Argument(cmd, _arg_name) => {
let path_completer = crate::completion::path::Completer::new();
let path_completer = crate::completion::path::Completer;
let completed_paths = path_completer.complete(context, partial);
match cmd.as_deref().unwrap_or("") {
"cd" => select_directory_suggestions(completed_paths),