fix: preserve path when completing intermediate directory (#10831)

<!--
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 #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->

- fixes #10766

# 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.
-->

If the partial supplied to the completion function is shorter than the
span, the cursor is in between the path, we are trying to complete an
intermediate directory. In such a case we:
- only suggest directory names
- don't append the slash since it is already present
- only complete the path till the component the cursor is on

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

Intermediate directories can be completed without erasing the rest of
the path.

# 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
> ```
-->

# 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.
-->
This commit is contained in:
Himadri Bhattacharjee 2023-10-31 18:09:14 +05:30 committed by GitHub
parent cf7040a215
commit 275dba82d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 54 additions and 8 deletions

View file

@ -1,5 +1,6 @@
use crate::completions::{matches, CompletionOptions}; use crate::completions::{matches, CompletionOptions};
use nu_path::home_dir; use nu_path::home_dir;
use nu_protocol::{engine::StateWorkingSet, Span};
use std::path::{is_separator, Component, Path, PathBuf, MAIN_SEPARATOR as SEP}; use std::path::{is_separator, Component, Path, PathBuf, MAIN_SEPARATOR as SEP};
fn complete_rec( fn complete_rec(
@ -160,3 +161,36 @@ pub fn escape_path(path: String, dir: bool) -> String {
path path
} }
} }
pub struct AdjustView {
pub prefix: String,
pub span: Span,
pub readjusted: bool,
}
pub fn adjust_if_intermediate(
prefix: &[u8],
working_set: &StateWorkingSet,
mut span: nu_protocol::Span,
) -> AdjustView {
let span_contents = String::from_utf8_lossy(working_set.get_span_contents(span)).to_string();
let mut prefix = String::from_utf8_lossy(prefix).to_string();
// A difference of 1 because of the cursor's unicode code point in between.
// Using .chars().count() because unicode and Windows.
let readjusted = span_contents.chars().count() - prefix.chars().count() > 1;
if readjusted {
let remnant: String = span_contents
.chars()
.skip(prefix.chars().count() + 1)
.take_while(|&c| !is_separator(c))
.collect();
prefix.push_str(&remnant);
span = Span::new(span.start, span.start + prefix.chars().count() + 1);
}
AdjustView {
prefix,
span,
readjusted,
}
}

View file

@ -1,4 +1,7 @@
use crate::completions::{completion_common::complete_item, Completer, CompletionOptions, SortBy}; use crate::completions::{
completion_common::{adjust_if_intermediate, complete_item, AdjustView},
Completer, CompletionOptions, SortBy,
};
use nu_protocol::{ use nu_protocol::{
engine::{EngineState, StateWorkingSet}, engine::{EngineState, StateWorkingSet},
levenshtein_distance, Span, levenshtein_distance, Span,
@ -21,19 +24,19 @@ impl DirectoryCompletion {
impl Completer for DirectoryCompletion { impl Completer for DirectoryCompletion {
fn fetch( fn fetch(
&mut self, &mut self,
_: &StateWorkingSet, working_set: &StateWorkingSet,
prefix: Vec<u8>, prefix: Vec<u8>,
span: Span, span: Span,
offset: usize, offset: usize,
_: usize, _: usize,
options: &CompletionOptions, options: &CompletionOptions,
) -> Vec<Suggestion> { ) -> Vec<Suggestion> {
let partial = String::from_utf8_lossy(&prefix).to_string(); let AdjustView { prefix, span, .. } = adjust_if_intermediate(&prefix, working_set, span);
// Filter only the folders // Filter only the folders
let output: Vec<_> = directory_completion( let output: Vec<_> = directory_completion(
span, span,
&partial, &prefix,
&self.engine_state.current_work_dir(), &self.engine_state.current_work_dir(),
options, options,
) )

View file

@ -1,4 +1,7 @@
use crate::completions::{completion_common::complete_item, Completer, CompletionOptions, SortBy}; use crate::completions::{
completion_common::{adjust_if_intermediate, complete_item, AdjustView},
Completer, CompletionOptions, SortBy,
};
use nu_protocol::{ use nu_protocol::{
engine::{EngineState, StateWorkingSet}, engine::{EngineState, StateWorkingSet},
levenshtein_distance, Span, levenshtein_distance, Span,
@ -21,15 +24,21 @@ impl FileCompletion {
impl Completer for FileCompletion { impl Completer for FileCompletion {
fn fetch( fn fetch(
&mut self, &mut self,
_: &StateWorkingSet, working_set: &StateWorkingSet,
prefix: Vec<u8>, prefix: Vec<u8>,
span: Span, span: Span,
offset: usize, offset: usize,
_: usize, _: usize,
options: &CompletionOptions, options: &CompletionOptions,
) -> Vec<Suggestion> { ) -> Vec<Suggestion> {
let prefix = String::from_utf8_lossy(&prefix).to_string(); let AdjustView {
let output: Vec<_> = file_path_completion( prefix,
span,
readjusted,
} = adjust_if_intermediate(&prefix, working_set, span);
let output: Vec<_> = complete_item(
readjusted,
span, span,
&prefix, &prefix,
&self.engine_state.current_work_dir(), &self.engine_state.current_work_dir(),