use std::path::PathBuf; use super::matchers::Matcher; use crate::completion::{Completer, CompletionContext, Suggestion}; const SEP: char = std::path::MAIN_SEPARATOR; pub struct PathCompleter; pub struct PathSuggestion { pub(crate) path: PathBuf, pub(crate) suggestion: Suggestion, } impl PathCompleter { pub fn path_suggestions(&self, partial: &str, matcher: &dyn Matcher) -> Vec { let expanded = nu_parser::expand_ndots(partial); let expanded = expanded.replace(std::path::is_separator, &SEP.to_string()); let expanded: &str = expanded.as_ref(); 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.is_empty() { PathBuf::from(".") } else { #[cfg(feature = "directories")] { let home_prefix = format!("~{}", SEP); if base_dir_name.starts_with(&home_prefix) { let mut home_dir = dirs_next::home_dir().unwrap_or_else(|| PathBuf::from("~")); home_dir.push(&base_dir_name[2..]); home_dir } else { PathBuf::from(base_dir_name) } } #[cfg(not(feature = "directories"))] { PathBuf::from(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 matcher.matches(partial, file_name.as_str()) { 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(PathSuggestion { path: entry.path(), suggestion: Suggestion { replacement: path, display: file_name, }, }) } else { None } }) }) .collect() } else { Vec::new() } } } impl Completer for PathCompleter { fn complete( &self, _ctx: &CompletionContext<'_>, partial: &str, matcher: &dyn Matcher, ) -> Vec { self.path_suggestions(partial, matcher) .into_iter() .map(|ps| ps.suggestion) .collect() } }