first pass of merging in warp (doesn't build)
This commit is contained in:
@@ -1,19 +1,19 @@
|
||||
use std::collections::HashSet;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::fs::DirEntry;
|
||||
use std::sync::Arc;
|
||||
|
||||
use galaxy_util::path::HOME_DIR_ENV_VAR_PREFIX;
|
||||
use async_trait::async_trait;
|
||||
use itertools::{iproduct, Itertools};
|
||||
use lazy_static::lazy_static;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typed_path::{TypedPath, TypedPathBuf};
|
||||
use warp_command_signatures::{IconType, PathSuggestionType};
|
||||
use galaxy_util::path::{ShellFamily, HOME_DIR_ENV_VAR_PREFIX};
|
||||
|
||||
use crate::completer::suggest::Priority;
|
||||
use crate::completer::{
|
||||
context::PathCompletionContext,
|
||||
matchers::MatchStrategy,
|
||||
suggest::{MatchedSuggestion, Suggestion, SuggestionType},
|
||||
};
|
||||
use crate::completer::context::{PathCompletionContext, PathSeparators};
|
||||
use crate::completer::matchers::MatchStrategy;
|
||||
use crate::completer::suggest::{MatchedSuggestion, Priority, Suggestion, SuggestionType};
|
||||
use crate::parsers::ParsedToken;
|
||||
|
||||
/// TODO(CORE-3074): This only applies to Unix.
|
||||
@@ -136,6 +136,127 @@ pub(crate) async fn sorted_directories_relative_to(
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Like `sorted_directories_relative_to`, but iterates `$CDPATH` in shell
|
||||
/// order (empty/`.` entry = pwd at that position; pwd appended as fallback if
|
||||
/// no such entry) so completions surface in the order `cd` would resolve them.
|
||||
pub(crate) async fn sorted_cd_directories(
|
||||
path: &ParsedToken,
|
||||
matcher: MatchStrategy,
|
||||
ctx: &dyn PathCompletionContext,
|
||||
) -> Vec<MatchedSuggestion> {
|
||||
if !is_cdpath_eligible_token(path.as_str()) {
|
||||
return sorted_directories_relative_to(path, matcher, ctx).await;
|
||||
}
|
||||
|
||||
let Some(cdpath) = ctx.cdpath() else {
|
||||
return sorted_directories_relative_to(path, matcher, ctx).await;
|
||||
};
|
||||
|
||||
let mut results: Vec<MatchedSuggestion> = Vec::new();
|
||||
let mut seen: HashSet<String> = HashSet::new();
|
||||
let mut pwd_searched = false;
|
||||
|
||||
let push_unique = |suggestions: Vec<MatchedSuggestion>,
|
||||
results: &mut Vec<MatchedSuggestion>,
|
||||
seen: &mut HashSet<String>| {
|
||||
for s in suggestions {
|
||||
if seen.insert(s.suggestion.display.to_string()) {
|
||||
results.push(s);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for entry in cdpath.split(':') {
|
||||
if entry.is_empty() || entry == "." {
|
||||
if pwd_searched {
|
||||
continue;
|
||||
}
|
||||
let pwd = sorted_directories_relative_to(path, matcher, ctx).await;
|
||||
push_unique(pwd, &mut results, &mut seen);
|
||||
pwd_searched = true;
|
||||
} else {
|
||||
let override_ctx = CdpathOverrideContext {
|
||||
inner: ctx,
|
||||
cdpath_pwd: resolve_cdpath_entry(entry, ctx),
|
||||
};
|
||||
let extra = sorted_directories_relative_to(path, matcher, &override_ctx).await;
|
||||
push_unique(extra, &mut results, &mut seen);
|
||||
}
|
||||
}
|
||||
|
||||
// Shell falls back to pwd when no `$CDPATH` entry matches. If no `.`/empty
|
||||
// entry positioned pwd already, append pwd matches now.
|
||||
if !pwd_searched {
|
||||
let pwd = sorted_directories_relative_to(path, matcher, ctx).await;
|
||||
push_unique(pwd, &mut results, &mut seen);
|
||||
}
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
/// Tilde-expand a `$CDPATH` entry against the shell's home dir, then resolve
|
||||
/// relative entries against the shell's pwd so `cd` matches shell behavior.
|
||||
fn resolve_cdpath_entry(entry: &str, ctx: &dyn PathCompletionContext) -> TypedPathBuf {
|
||||
let expanded = if entry == "~" {
|
||||
ctx.home_directory().unwrap_or_default().to_owned()
|
||||
} else if let Some(rest) = entry.strip_prefix("~/") {
|
||||
format!("{}/{}", ctx.home_directory().unwrap_or_default(), rest)
|
||||
} else {
|
||||
entry.to_owned()
|
||||
};
|
||||
|
||||
let resolved = TypedPathBuf::from(expanded.as_str());
|
||||
if resolved.is_absolute() {
|
||||
resolved
|
||||
} else {
|
||||
ctx.pwd().join(expanded)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_cdpath_eligible_token(token: &str) -> bool {
|
||||
!(token.starts_with('/')
|
||||
|| token.starts_with('~')
|
||||
|| token.starts_with("./")
|
||||
|| token.starts_with("../")
|
||||
|| token == "."
|
||||
|| token == "..")
|
||||
}
|
||||
|
||||
/// Wraps a `PathCompletionContext` and overrides only `pwd()` so we can reuse
|
||||
/// the existing engine to list directories under a `$CDPATH` entry.
|
||||
struct CdpathOverrideContext<'a> {
|
||||
inner: &'a dyn PathCompletionContext,
|
||||
cdpath_pwd: TypedPathBuf,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<'a> PathCompletionContext for CdpathOverrideContext<'a> {
|
||||
async fn list_directory_entries(&self, directory: TypedPathBuf) -> Arc<Vec<EngineDirEntry>> {
|
||||
self.inner.list_directory_entries(directory).await
|
||||
}
|
||||
|
||||
fn home_directory(&self) -> Option<&str> {
|
||||
self.inner.home_directory()
|
||||
}
|
||||
|
||||
fn cdpath(&self) -> Option<&str> {
|
||||
// Avoid recursing — the outer call already iterates entries.
|
||||
None
|
||||
}
|
||||
|
||||
fn shell_family(&self) -> ShellFamily {
|
||||
self.inner.shell_family()
|
||||
}
|
||||
|
||||
fn pwd(&self) -> TypedPath<'_> {
|
||||
self.cdpath_pwd.to_path()
|
||||
}
|
||||
|
||||
fn path_separators(&self) -> PathSeparators {
|
||||
self.inner.path_separators()
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn sorted_paths_relative_to(
|
||||
path: &ParsedToken,
|
||||
matcher: MatchStrategy,
|
||||
@@ -307,5 +428,5 @@ impl SplitPath {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "path_test.rs"]
|
||||
#[path = "path_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
Reference in New Issue
Block a user