mirror of
https://github.com/nushell/nushell
synced 2025-01-14 14:14:13 +00:00
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:
parent
bacc1f6317
commit
5042f19d1b
10 changed files with 107 additions and 36 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2843,6 +2843,7 @@ dependencies = [
|
||||||
"fuzzy-matcher",
|
"fuzzy-matcher",
|
||||||
"is_executable",
|
"is_executable",
|
||||||
"log",
|
"log",
|
||||||
|
"lscolors",
|
||||||
"miette",
|
"miette",
|
||||||
"nu-ansi-term",
|
"nu-ansi-term",
|
||||||
"nu-cmd-base",
|
"nu-cmd-base",
|
||||||
|
|
|
@ -34,6 +34,7 @@ fuzzy-matcher = "0.3"
|
||||||
is_executable = "1.0"
|
is_executable = "1.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
miette = { version = "7.0", features = ["fancy-no-backtrace"] }
|
miette = { version = "7.0", features = ["fancy-no-backtrace"] }
|
||||||
|
lscolors = { version = "0.17", default-features = false, features = ["nu-ansi-term"] }
|
||||||
once_cell = "1.18"
|
once_cell = "1.18"
|
||||||
percent-encoding = "2"
|
percent-encoding = "2"
|
||||||
pathdiff = "0.2"
|
pathdiff = "0.2"
|
||||||
|
|
|
@ -266,8 +266,10 @@ impl NuCompleter {
|
||||||
|| prev_expr_str == b"overlay use"
|
|| prev_expr_str == b"overlay use"
|
||||||
|| prev_expr_str == b"source-env"
|
|| prev_expr_str == b"source-env"
|
||||||
{
|
{
|
||||||
let mut completer =
|
let mut completer = DotNuCompletion::new(
|
||||||
DotNuCompletion::new(self.engine_state.clone());
|
self.engine_state.clone(),
|
||||||
|
self.stack.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
return self.process_completion(
|
return self.process_completion(
|
||||||
&mut completer,
|
&mut completer,
|
||||||
|
@ -278,8 +280,10 @@ impl NuCompleter {
|
||||||
pos,
|
pos,
|
||||||
);
|
);
|
||||||
} else if prev_expr_str == b"ls" {
|
} else if prev_expr_str == b"ls" {
|
||||||
let mut completer =
|
let mut completer = FileCompletion::new(
|
||||||
FileCompletion::new(self.engine_state.clone());
|
self.engine_state.clone(),
|
||||||
|
self.stack.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
return self.process_completion(
|
return self.process_completion(
|
||||||
&mut completer,
|
&mut completer,
|
||||||
|
@ -313,8 +317,10 @@ impl NuCompleter {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
FlatShape::Directory => {
|
FlatShape::Directory => {
|
||||||
let mut completer =
|
let mut completer = DirectoryCompletion::new(
|
||||||
DirectoryCompletion::new(self.engine_state.clone());
|
self.engine_state.clone(),
|
||||||
|
self.stack.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
return self.process_completion(
|
return self.process_completion(
|
||||||
&mut completer,
|
&mut completer,
|
||||||
|
@ -326,8 +332,10 @@ impl NuCompleter {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
FlatShape::Filepath | FlatShape::GlobPattern => {
|
FlatShape::Filepath | FlatShape::GlobPattern => {
|
||||||
let mut completer =
|
let mut completer = FileCompletion::new(
|
||||||
FileCompletion::new(self.engine_state.clone());
|
self.engine_state.clone(),
|
||||||
|
self.stack.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
return self.process_completion(
|
return self.process_completion(
|
||||||
&mut completer,
|
&mut completer,
|
||||||
|
@ -374,8 +382,10 @@ impl NuCompleter {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for file completion
|
// Check for file completion
|
||||||
let mut completer =
|
let mut completer = FileCompletion::new(
|
||||||
FileCompletion::new(self.engine_state.clone());
|
self.engine_state.clone(),
|
||||||
|
self.stack.clone(),
|
||||||
|
);
|
||||||
out = self.process_completion(
|
out = self.process_completion(
|
||||||
&mut completer,
|
&mut completer,
|
||||||
&working_set,
|
&working_set,
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
use crate::completions::{matches, CompletionOptions};
|
use crate::completions::{matches, CompletionOptions};
|
||||||
|
use nu_ansi_term::Style;
|
||||||
|
use nu_engine::env_to_string;
|
||||||
use nu_path::home_dir;
|
use nu_path::home_dir;
|
||||||
|
use nu_protocol::engine::{EngineState, Stack};
|
||||||
use nu_protocol::{engine::StateWorkingSet, Span};
|
use nu_protocol::{engine::StateWorkingSet, Span};
|
||||||
|
use nu_utils::get_ls_colors;
|
||||||
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(
|
||||||
|
@ -92,10 +96,21 @@ pub fn complete_item(
|
||||||
partial: &str,
|
partial: &str,
|
||||||
cwd: &str,
|
cwd: &str,
|
||||||
options: &CompletionOptions,
|
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 partial = surround_remove(partial);
|
||||||
let isdir = partial.ends_with(is_separator);
|
let isdir = partial.ends_with(is_separator);
|
||||||
let cwd_pathbuf = Path::new(cwd).to_path_buf();
|
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 original_cwd = OriginalCwd::None;
|
||||||
let mut components = Path::new(&partial).components().peekable();
|
let mut components = Path::new(&partial).components().peekable();
|
||||||
let mut cwd = match components.peek().cloned() {
|
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)
|
complete_rec(partial.as_slice(), &cwd, options, want_directory, isdir)
|
||||||
.into_iter()
|
.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()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,9 @@ use crate::completions::{
|
||||||
completion_common::{adjust_if_intermediate, complete_item, AdjustView},
|
completion_common::{adjust_if_intermediate, complete_item, AdjustView},
|
||||||
Completer, CompletionOptions, SortBy,
|
Completer, CompletionOptions, SortBy,
|
||||||
};
|
};
|
||||||
|
use nu_ansi_term::Style;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
levenshtein_distance, Span,
|
levenshtein_distance, Span,
|
||||||
};
|
};
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
|
@ -13,11 +14,15 @@ use std::sync::Arc;
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DirectoryCompletion {
|
pub struct DirectoryCompletion {
|
||||||
engine_state: Arc<EngineState>,
|
engine_state: Arc<EngineState>,
|
||||||
|
stack: Stack,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DirectoryCompletion {
|
impl DirectoryCompletion {
|
||||||
pub fn new(engine_state: Arc<EngineState>) -> Self {
|
pub fn new(engine_state: Arc<EngineState>, stack: Stack) -> Self {
|
||||||
Self { engine_state }
|
Self {
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,12 +44,14 @@ impl Completer for DirectoryCompletion {
|
||||||
&prefix,
|
&prefix,
|
||||||
&self.engine_state.current_work_dir(),
|
&self.engine_state.current_work_dir(),
|
||||||
options,
|
options,
|
||||||
|
self.engine_state.as_ref(),
|
||||||
|
&self.stack,
|
||||||
)
|
)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(move |x| Suggestion {
|
.map(move |x| Suggestion {
|
||||||
value: x.1,
|
value: x.1,
|
||||||
description: None,
|
description: None,
|
||||||
style: None,
|
style: x.2,
|
||||||
extra: None,
|
extra: None,
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: x.0.start - offset,
|
start: x.0.start - offset,
|
||||||
|
@ -113,6 +120,8 @@ pub fn directory_completion(
|
||||||
partial: &str,
|
partial: &str,
|
||||||
cwd: &str,
|
cwd: &str,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<(nu_protocol::Span, String)> {
|
engine_state: &EngineState,
|
||||||
complete_item(true, span, partial, cwd, options)
|
stack: &Stack,
|
||||||
|
) -> Vec<(nu_protocol::Span, String, Option<Style>)> {
|
||||||
|
complete_item(true, span, partial, cwd, options, engine_state, stack)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::completions::{file_path_completion, Completer, CompletionOptions, SortBy};
|
use crate::completions::{file_path_completion, Completer, CompletionOptions, SortBy};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
Span,
|
Span,
|
||||||
};
|
};
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
|
@ -12,11 +12,15 @@ use std::{
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DotNuCompletion {
|
pub struct DotNuCompletion {
|
||||||
engine_state: Arc<EngineState>,
|
engine_state: Arc<EngineState>,
|
||||||
|
stack: Stack,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DotNuCompletion {
|
impl DotNuCompletion {
|
||||||
pub fn new(engine_state: Arc<EngineState>) -> Self {
|
pub fn new(engine_state: Arc<EngineState>, stack: Stack) -> Self {
|
||||||
Self { engine_state }
|
Self {
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +96,14 @@ impl Completer for DotNuCompletion {
|
||||||
let output: Vec<Suggestion> = search_dirs
|
let output: Vec<Suggestion> = search_dirs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|search_dir| {
|
.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
|
completions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(move |it| {
|
.filter(move |it| {
|
||||||
|
@ -111,7 +122,7 @@ impl Completer for DotNuCompletion {
|
||||||
.map(move |x| Suggestion {
|
.map(move |x| Suggestion {
|
||||||
value: x.1,
|
value: x.1,
|
||||||
description: None,
|
description: None,
|
||||||
style: None,
|
style: x.2,
|
||||||
extra: None,
|
extra: None,
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: x.0.start - offset,
|
start: x.0.start - offset,
|
||||||
|
|
|
@ -2,8 +2,9 @@ use crate::completions::{
|
||||||
completion_common::{adjust_if_intermediate, complete_item, AdjustView},
|
completion_common::{adjust_if_intermediate, complete_item, AdjustView},
|
||||||
Completer, CompletionOptions, SortBy,
|
Completer, CompletionOptions, SortBy,
|
||||||
};
|
};
|
||||||
|
use nu_ansi_term::Style;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
levenshtein_distance, Span,
|
levenshtein_distance, Span,
|
||||||
};
|
};
|
||||||
use nu_utils::IgnoreCaseExt;
|
use nu_utils::IgnoreCaseExt;
|
||||||
|
@ -14,11 +15,15 @@ use std::sync::Arc;
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct FileCompletion {
|
pub struct FileCompletion {
|
||||||
engine_state: Arc<EngineState>,
|
engine_state: Arc<EngineState>,
|
||||||
|
stack: Stack,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileCompletion {
|
impl FileCompletion {
|
||||||
pub fn new(engine_state: Arc<EngineState>) -> Self {
|
pub fn new(engine_state: Arc<EngineState>, stack: Stack) -> Self {
|
||||||
Self { engine_state }
|
Self {
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,12 +49,14 @@ impl Completer for FileCompletion {
|
||||||
&prefix,
|
&prefix,
|
||||||
&self.engine_state.current_work_dir(),
|
&self.engine_state.current_work_dir(),
|
||||||
options,
|
options,
|
||||||
|
self.engine_state.as_ref(),
|
||||||
|
&self.stack,
|
||||||
)
|
)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(move |x| Suggestion {
|
.map(move |x| Suggestion {
|
||||||
value: x.1,
|
value: x.1,
|
||||||
description: None,
|
description: None,
|
||||||
style: None,
|
style: x.2,
|
||||||
extra: None,
|
extra: None,
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: x.0.start - offset,
|
start: x.0.start - offset,
|
||||||
|
@ -118,8 +125,10 @@ pub fn file_path_completion(
|
||||||
partial: &str,
|
partial: &str,
|
||||||
cwd: &str,
|
cwd: &str,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<(nu_protocol::Span, String)> {
|
engine_state: &EngineState,
|
||||||
complete_item(false, span, partial, cwd, options)
|
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 {
|
pub fn matches(partial: &str, from: &str, options: &CompletionOptions) -> bool {
|
||||||
|
|
|
@ -897,13 +897,10 @@ fn render_path_name(
|
||||||
|
|
||||||
let stripped_path = nu_utils::strip_ansi_unlikely(path);
|
let stripped_path = nu_utils::strip_ansi_unlikely(path);
|
||||||
|
|
||||||
let (style, has_metadata) = match std::fs::symlink_metadata(stripped_path.as_ref()) {
|
let metadata = std::fs::symlink_metadata(stripped_path.as_ref());
|
||||||
Ok(metadata) => (
|
let has_metadata = metadata.is_ok();
|
||||||
ls_colors.style_for_path_with_metadata(stripped_path.as_ref(), Some(&metadata)),
|
let style =
|
||||||
true,
|
ls_colors.style_for_path_with_metadata(stripped_path.as_ref(), metadata.ok().as_ref());
|
||||||
),
|
|
||||||
Err(_) => (ls_colors.style_for_path(stripped_path.as_ref()), false),
|
|
||||||
};
|
|
||||||
|
|
||||||
// clickable links don't work in remote SSH sessions
|
// clickable links don't work in remote SSH sessions
|
||||||
let in_ssh_session = std::env::var("SSH_CLIENT").is_ok();
|
let in_ssh_session = std::env::var("SSH_CLIENT").is_ok();
|
||||||
|
|
|
@ -89,6 +89,7 @@ pub struct Config {
|
||||||
pub error_style: ErrorStyle,
|
pub error_style: ErrorStyle,
|
||||||
pub use_kitty_protocol: bool,
|
pub use_kitty_protocol: bool,
|
||||||
pub highlight_resolved_externals: bool,
|
pub highlight_resolved_externals: bool,
|
||||||
|
pub use_ls_colors_completions: bool,
|
||||||
/// Configuration for plugins.
|
/// Configuration for plugins.
|
||||||
///
|
///
|
||||||
/// Users can provide configuration for a plugin through this entry. The entry name must
|
/// 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,
|
enable_external_completion: true,
|
||||||
max_external_completion_results: 100,
|
max_external_completion_results: 100,
|
||||||
external_completer: None,
|
external_completer: None,
|
||||||
|
use_ls_colors_completions: false,
|
||||||
|
|
||||||
filesize_metric: false,
|
filesize_metric: false,
|
||||||
filesize_format: "auto".into(),
|
filesize_format: "auto".into(),
|
||||||
|
@ -349,6 +351,9 @@ impl Value {
|
||||||
*value = reconstruct_external(&config, span);
|
*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);
|
report_invalid_key(&[key, key2], span, &mut errors);
|
||||||
return false;
|
return false;
|
||||||
|
@ -366,6 +371,7 @@ impl Value {
|
||||||
"algorithm" => config.completion_algorithm.reconstruct_value(span),
|
"algorithm" => config.completion_algorithm.reconstruct_value(span),
|
||||||
"case_sensitive" => Value::bool(config.case_sensitive_completions, span),
|
"case_sensitive" => Value::bool(config.case_sensitive_completions, span),
|
||||||
"external" => reconstruct_external(&config, span),
|
"external" => reconstruct_external(&config, span),
|
||||||
|
"use_ls_colors" => Value::bool(config.use_ls_colors_completions, span),
|
||||||
},
|
},
|
||||||
span,
|
span,
|
||||||
);
|
);
|
||||||
|
|
|
@ -212,6 +212,7 @@ $env.config = {
|
||||||
max_results: 100 # setting it lower can improve completion performance at the cost of omitting some options
|
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
|
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: {
|
filesize: {
|
||||||
|
|
Loading…
Reference in a new issue