colored file-like completions (#11702)

<!--
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!
-->

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

`ls` and other file completions uses `LS_COLORS`.

![maim-2024 01 31 21 34
31](https://github.com/nushell/nushell/assets/15631555/d5c3813f-77b5-4391-aa0b-4b2125e5aca5)


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

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

---------

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
This commit is contained in:
Steven 2024-02-08 12:29:28 -08:00 committed by GitHub
parent bacc1f6317
commit 5042f19d1b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 107 additions and 36 deletions

1
Cargo.lock generated
View File

@ -2843,6 +2843,7 @@ dependencies = [
"fuzzy-matcher",
"is_executable",
"log",
"lscolors",
"miette",
"nu-ansi-term",
"nu-cmd-base",

View File

@ -34,6 +34,7 @@ fuzzy-matcher = "0.3"
is_executable = "1.0"
log = "0.4"
miette = { version = "7.0", features = ["fancy-no-backtrace"] }
lscolors = { version = "0.17", default-features = false, features = ["nu-ansi-term"] }
once_cell = "1.18"
percent-encoding = "2"
pathdiff = "0.2"

View File

@ -266,8 +266,10 @@ impl NuCompleter {
|| prev_expr_str == b"overlay use"
|| prev_expr_str == b"source-env"
{
let mut completer =
DotNuCompletion::new(self.engine_state.clone());
let mut completer = DotNuCompletion::new(
self.engine_state.clone(),
self.stack.clone(),
);
return self.process_completion(
&mut completer,
@ -278,8 +280,10 @@ impl NuCompleter {
pos,
);
} else if prev_expr_str == b"ls" {
let mut completer =
FileCompletion::new(self.engine_state.clone());
let mut completer = FileCompletion::new(
self.engine_state.clone(),
self.stack.clone(),
);
return self.process_completion(
&mut completer,
@ -313,8 +317,10 @@ impl NuCompleter {
);
}
FlatShape::Directory => {
let mut completer =
DirectoryCompletion::new(self.engine_state.clone());
let mut completer = DirectoryCompletion::new(
self.engine_state.clone(),
self.stack.clone(),
);
return self.process_completion(
&mut completer,
@ -326,8 +332,10 @@ impl NuCompleter {
);
}
FlatShape::Filepath | FlatShape::GlobPattern => {
let mut completer =
FileCompletion::new(self.engine_state.clone());
let mut completer = FileCompletion::new(
self.engine_state.clone(),
self.stack.clone(),
);
return self.process_completion(
&mut completer,
@ -374,8 +382,10 @@ impl NuCompleter {
}
// Check for file completion
let mut completer =
FileCompletion::new(self.engine_state.clone());
let mut completer = FileCompletion::new(
self.engine_state.clone(),
self.stack.clone(),
);
out = self.process_completion(
&mut completer,
&working_set,

View File

@ -1,6 +1,10 @@
use crate::completions::{matches, CompletionOptions};
use nu_ansi_term::Style;
use nu_engine::env_to_string;
use nu_path::home_dir;
use nu_protocol::engine::{EngineState, Stack};
use nu_protocol::{engine::StateWorkingSet, Span};
use nu_utils::get_ls_colors;
use std::path::{is_separator, Component, Path, PathBuf, MAIN_SEPARATOR as SEP};
fn complete_rec(
@ -92,10 +96,21 @@ pub fn complete_item(
partial: &str,
cwd: &str,
options: &CompletionOptions,
) -> Vec<(nu_protocol::Span, String)> {
engine_state: &EngineState,
stack: &Stack,
) -> Vec<(nu_protocol::Span, String, Option<Style>)> {
let partial = surround_remove(partial);
let isdir = partial.ends_with(is_separator);
let cwd_pathbuf = Path::new(cwd).to_path_buf();
let ls_colors = (engine_state.config.use_ls_colors_completions
&& engine_state.config.use_ansi_coloring)
.then(|| {
let ls_colors_env_str = match stack.get_env_var(engine_state, "LS_COLORS") {
Some(v) => env_to_string("LS_COLORS", &v, engine_state, stack).ok(),
None => None,
};
get_ls_colors(ls_colors_env_str)
});
let mut original_cwd = OriginalCwd::None;
let mut components = Path::new(&partial).components().peekable();
let mut cwd = match components.peek().cloned() {
@ -148,7 +163,18 @@ pub fn complete_item(
complete_rec(partial.as_slice(), &cwd, options, want_directory, isdir)
.into_iter()
.map(|p| (span, escape_path(original_cwd.apply(&p), want_directory)))
.map(|p| {
let path = original_cwd.apply(&p);
let style = ls_colors.as_ref().map(|lsc| {
lsc.style_for_path_with_metadata(
&path,
std::fs::symlink_metadata(&path).ok().as_ref(),
)
.map(lscolors::Style::to_nu_ansi_term_style)
.unwrap_or_default()
});
(span, escape_path(path, want_directory), style)
})
.collect()
}

View File

@ -2,8 +2,9 @@ use crate::completions::{
completion_common::{adjust_if_intermediate, complete_item, AdjustView},
Completer, CompletionOptions, SortBy,
};
use nu_ansi_term::Style;
use nu_protocol::{
engine::{EngineState, StateWorkingSet},
engine::{EngineState, Stack, StateWorkingSet},
levenshtein_distance, Span,
};
use reedline::Suggestion;
@ -13,11 +14,15 @@ use std::sync::Arc;
#[derive(Clone)]
pub struct DirectoryCompletion {
engine_state: Arc<EngineState>,
stack: Stack,
}
impl DirectoryCompletion {
pub fn new(engine_state: Arc<EngineState>) -> Self {
Self { engine_state }
pub fn new(engine_state: Arc<EngineState>, stack: Stack) -> Self {
Self {
engine_state,
stack,
}
}
}
@ -39,12 +44,14 @@ impl Completer for DirectoryCompletion {
&prefix,
&self.engine_state.current_work_dir(),
options,
self.engine_state.as_ref(),
&self.stack,
)
.into_iter()
.map(move |x| Suggestion {
value: x.1,
description: None,
style: None,
style: x.2,
extra: None,
span: reedline::Span {
start: x.0.start - offset,
@ -113,6 +120,8 @@ pub fn directory_completion(
partial: &str,
cwd: &str,
options: &CompletionOptions,
) -> Vec<(nu_protocol::Span, String)> {
complete_item(true, span, partial, cwd, options)
engine_state: &EngineState,
stack: &Stack,
) -> Vec<(nu_protocol::Span, String, Option<Style>)> {
complete_item(true, span, partial, cwd, options, engine_state, stack)
}

View File

@ -1,6 +1,6 @@
use crate::completions::{file_path_completion, Completer, CompletionOptions, SortBy};
use nu_protocol::{
engine::{EngineState, StateWorkingSet},
engine::{EngineState, Stack, StateWorkingSet},
Span,
};
use reedline::Suggestion;
@ -12,11 +12,15 @@ use std::{
#[derive(Clone)]
pub struct DotNuCompletion {
engine_state: Arc<EngineState>,
stack: Stack,
}
impl DotNuCompletion {
pub fn new(engine_state: Arc<EngineState>) -> Self {
Self { engine_state }
pub fn new(engine_state: Arc<EngineState>, stack: Stack) -> Self {
Self {
engine_state,
stack,
}
}
}
@ -92,7 +96,14 @@ impl Completer for DotNuCompletion {
let output: Vec<Suggestion> = search_dirs
.into_iter()
.flat_map(|search_dir| {
let completions = file_path_completion(span, &partial, &search_dir, options);
let completions = file_path_completion(
span,
&partial,
&search_dir,
options,
self.engine_state.as_ref(),
&self.stack,
);
completions
.into_iter()
.filter(move |it| {
@ -111,7 +122,7 @@ impl Completer for DotNuCompletion {
.map(move |x| Suggestion {
value: x.1,
description: None,
style: None,
style: x.2,
extra: None,
span: reedline::Span {
start: x.0.start - offset,

View File

@ -2,8 +2,9 @@ use crate::completions::{
completion_common::{adjust_if_intermediate, complete_item, AdjustView},
Completer, CompletionOptions, SortBy,
};
use nu_ansi_term::Style;
use nu_protocol::{
engine::{EngineState, StateWorkingSet},
engine::{EngineState, Stack, StateWorkingSet},
levenshtein_distance, Span,
};
use nu_utils::IgnoreCaseExt;
@ -14,11 +15,15 @@ use std::sync::Arc;
#[derive(Clone)]
pub struct FileCompletion {
engine_state: Arc<EngineState>,
stack: Stack,
}
impl FileCompletion {
pub fn new(engine_state: Arc<EngineState>) -> Self {
Self { engine_state }
pub fn new(engine_state: Arc<EngineState>, stack: Stack) -> Self {
Self {
engine_state,
stack,
}
}
}
@ -44,12 +49,14 @@ impl Completer for FileCompletion {
&prefix,
&self.engine_state.current_work_dir(),
options,
self.engine_state.as_ref(),
&self.stack,
)
.into_iter()
.map(move |x| Suggestion {
value: x.1,
description: None,
style: None,
style: x.2,
extra: None,
span: reedline::Span {
start: x.0.start - offset,
@ -118,8 +125,10 @@ pub fn file_path_completion(
partial: &str,
cwd: &str,
options: &CompletionOptions,
) -> Vec<(nu_protocol::Span, String)> {
complete_item(false, span, partial, cwd, options)
engine_state: &EngineState,
stack: &Stack,
) -> Vec<(nu_protocol::Span, String, Option<Style>)> {
complete_item(false, span, partial, cwd, options, engine_state, stack)
}
pub fn matches(partial: &str, from: &str, options: &CompletionOptions) -> bool {

View File

@ -897,13 +897,10 @@ fn render_path_name(
let stripped_path = nu_utils::strip_ansi_unlikely(path);
let (style, has_metadata) = match std::fs::symlink_metadata(stripped_path.as_ref()) {
Ok(metadata) => (
ls_colors.style_for_path_with_metadata(stripped_path.as_ref(), Some(&metadata)),
true,
),
Err(_) => (ls_colors.style_for_path(stripped_path.as_ref()), false),
};
let metadata = std::fs::symlink_metadata(stripped_path.as_ref());
let has_metadata = metadata.is_ok();
let style =
ls_colors.style_for_path_with_metadata(stripped_path.as_ref(), metadata.ok().as_ref());
// clickable links don't work in remote SSH sessions
let in_ssh_session = std::env::var("SSH_CLIENT").is_ok();

View File

@ -89,6 +89,7 @@ pub struct Config {
pub error_style: ErrorStyle,
pub use_kitty_protocol: bool,
pub highlight_resolved_externals: bool,
pub use_ls_colors_completions: bool,
/// Configuration for plugins.
///
/// Users can provide configuration for a plugin through this entry. The entry name must
@ -129,6 +130,7 @@ impl Default for Config {
enable_external_completion: true,
max_external_completion_results: 100,
external_completer: None,
use_ls_colors_completions: false,
filesize_metric: false,
filesize_format: "auto".into(),
@ -349,6 +351,9 @@ impl Value {
*value = reconstruct_external(&config, span);
}
}
"use_ls_colors" => {
process_bool_config(value, &mut errors, &mut config.use_ls_colors_completions);
}
_ => {
report_invalid_key(&[key, key2], span, &mut errors);
return false;
@ -366,6 +371,7 @@ impl Value {
"algorithm" => config.completion_algorithm.reconstruct_value(span),
"case_sensitive" => Value::bool(config.case_sensitive_completions, span),
"external" => reconstruct_external(&config, span),
"use_ls_colors" => Value::bool(config.use_ls_colors_completions, span),
},
span,
);

View File

@ -212,6 +212,7 @@ $env.config = {
max_results: 100 # setting it lower can improve completion performance at the cost of omitting some options
completer: null # check 'carapace_completer' above as an example
}
use_ls_colors: false # set this to true to enable file/path/directory completions using LS_COLORS
}
filesize: {