<!-- if this PR closes one or more issues, you can automatically link the PR with them by using one of the [*linking keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword), e.g. - this PR should close #5683 you can also mention related issues, PRs or discussions! --> # Description <!-- Thank you for improving Nushell. Please, check our [contributing guide](../CONTRIBUTING.md) and talk to the core team before making major changes. Description of your pull request goes here. **Provide examples and/or screenshots** if your changes affect the user experience. --> This PR allows tab completion for nested directories while only specifying a part of the directory names. To illustrate this, if I type `tar/de/inc` and hit tab, it autocompletes to `./target/debug/incremental`. # User-Facing Changes <!-- List of all changes that impact the user experience here. This helps us keep track of breaking changes. --> Nested paths can be tab completed by typing lesser characters. # Tests + Formatting <!-- Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass (on Windows make sure to [enable developer mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging)) - `cargo run -- -c "use std testing; testing run-tests --path crates/nu-std"` to run the tests for the standard library > **Note** > from `nushell` you can also use the `toolkit` as follows > ```bash > use toolkit.nu # or use an `env_change` hook to activate it automatically > toolkit check pr > ``` --> Tests cases are added. # After Submitting <!-- If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date. -->
119 lines
3.3 KiB
Rust
119 lines
3.3 KiB
Rust
use crate::completions::{completion_common::complete_item, Completer, CompletionOptions};
|
|
use nu_protocol::{
|
|
engine::{EngineState, StateWorkingSet},
|
|
levenshtein_distance, Span,
|
|
};
|
|
use reedline::Suggestion;
|
|
use std::path::Path;
|
|
use std::sync::Arc;
|
|
|
|
use super::SortBy;
|
|
|
|
const SEP: char = std::path::MAIN_SEPARATOR;
|
|
|
|
#[derive(Clone)]
|
|
pub struct DirectoryCompletion {
|
|
engine_state: Arc<EngineState>,
|
|
}
|
|
|
|
impl DirectoryCompletion {
|
|
pub fn new(engine_state: Arc<EngineState>) -> Self {
|
|
Self { engine_state }
|
|
}
|
|
}
|
|
|
|
impl Completer for DirectoryCompletion {
|
|
fn fetch(
|
|
&mut self,
|
|
_: &StateWorkingSet,
|
|
prefix: Vec<u8>,
|
|
span: Span,
|
|
offset: usize,
|
|
_: usize,
|
|
options: &CompletionOptions,
|
|
) -> Vec<Suggestion> {
|
|
let partial = String::from_utf8_lossy(&prefix).to_string();
|
|
|
|
// Filter only the folders
|
|
let output: Vec<_> = directory_completion(
|
|
span,
|
|
&partial,
|
|
&self.engine_state.current_work_dir(),
|
|
options,
|
|
)
|
|
.into_iter()
|
|
.map(move |x| Suggestion {
|
|
value: x.1,
|
|
description: None,
|
|
extra: None,
|
|
span: reedline::Span {
|
|
start: x.0.start - offset,
|
|
end: x.0.end - offset,
|
|
},
|
|
append_whitespace: false,
|
|
})
|
|
.collect();
|
|
|
|
output
|
|
}
|
|
|
|
// Sort results prioritizing the non hidden folders
|
|
fn sort(&self, items: Vec<Suggestion>, prefix: Vec<u8>) -> Vec<Suggestion> {
|
|
let prefix_str = String::from_utf8_lossy(&prefix).to_string();
|
|
|
|
// Sort items
|
|
let mut sorted_items = items;
|
|
|
|
match self.get_sort_by() {
|
|
SortBy::Ascending => {
|
|
sorted_items.sort_by(|a, b| {
|
|
// Ignore trailing slashes in folder names when sorting
|
|
a.value
|
|
.trim_end_matches(SEP)
|
|
.cmp(b.value.trim_end_matches(SEP))
|
|
});
|
|
}
|
|
SortBy::LevenshteinDistance => {
|
|
sorted_items.sort_by(|a, b| {
|
|
let a_distance = levenshtein_distance(&prefix_str, &a.value);
|
|
let b_distance = levenshtein_distance(&prefix_str, &b.value);
|
|
a_distance.cmp(&b_distance)
|
|
});
|
|
}
|
|
_ => (),
|
|
}
|
|
|
|
// Separate the results between hidden and non hidden
|
|
let mut hidden: Vec<Suggestion> = vec![];
|
|
let mut non_hidden: Vec<Suggestion> = vec![];
|
|
|
|
for item in sorted_items.into_iter() {
|
|
let item_path = Path::new(&item.value);
|
|
|
|
if let Some(value) = item_path.file_name() {
|
|
if let Some(value) = value.to_str() {
|
|
if value.starts_with('.') {
|
|
hidden.push(item);
|
|
} else {
|
|
non_hidden.push(item);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Append the hidden folders to the non hidden vec to avoid creating a new vec
|
|
non_hidden.append(&mut hidden);
|
|
|
|
non_hidden
|
|
}
|
|
}
|
|
|
|
pub fn directory_completion(
|
|
span: nu_protocol::Span,
|
|
partial: &str,
|
|
cwd: &str,
|
|
options: &CompletionOptions,
|
|
) -> Vec<(nu_protocol::Span, String)> {
|
|
complete_item(true, span, partial, cwd, options)
|
|
}
|